@Override
  public void attachToVLAN(@Nonnull String providerVpnId, @Nonnull String providerVlanId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "attachVPNToVLAN");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(provider.getContext(), ELBMethod.ATTACH_VPN_GATEWAY);
      EC2Method method;

      parameters.put("VpcId", providerVlanId);
      parameters.put("VpnGatewayId", providerVpnId);
      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        method.invoke();
      } catch (EC2Exception e) {
        logger.error(e.getSummary());
        e.printStackTrace();
        throw new CloudException(e);
      }
    } finally {
      APITrace.end();
    }
  }
  @Override
  public void deleteDnsRecords(@Nonnull DNSRecord... dnsRecords)
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.deleteDnsRecords");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        logger.error("No context exists for this request");
        throw new InternalException("No context exists for this request");
      }
      for (DNSRecord record : dnsRecords) {
        NovaMethod method = new NovaMethod(provider);

        List<String> ids = lookupRecord(record);

        for (String id : ids) {
          method.deleteResource(
              SERVICE, RESOURCE, record.getProviderZoneId() + "/records/" + id, null);
        }
      }
    } finally {
      APITrace.end();
    }
  }
  @Nullable
  @Override
  public OfflineStoreRequest getRequest(@Nonnull String bucket, @Nonnull String requestId)
      throws CloudException, InternalException {
    APITrace.begin(getProvider(), "Blob.getRequest");
    try {

      final GlacierMethod method =
          GlacierMethod.build(getProvider(), GlacierAction.DESCRIBE_JOB)
              .vaultId(bucket)
              .jobId(requestId)
              .toMethod();

      try {
        final JSONObject jsonObject = method.invokeJson();
        return loadRequestJson(jsonObject, bucket);

      } catch (GlacierException e) {
        if (e.getHttpCode() == 404) {
          return null;
        }
        throw e;
      } catch (JSONException e) {
        throw new CloudException(e);
      }
    } finally {
      APITrace.end();
    }
  }
  @Override
  public @Nonnull Blob createBucket(@Nonnull String bucketName, boolean findFreeName)
      throws InternalException, CloudException {
    APITrace.begin(getProvider(), "Blob.createBucket");
    try {
      if (bucketName.contains("/")) {
        throw new OperationNotSupportedException("Nested buckets are not supported");
      }
      String regionId = getContext().getRegionId();

      if (regionId == null) {
        throw new InternalException("No region ID was specified for this request");
      }

      GlacierMethod method =
          GlacierMethod.build(getProvider(), GlacierAction.CREATE_VAULT)
              .vaultId(bucketName)
              .toMethod();
      method.invoke();
      String url = method.getUrl();
      return Blob.getInstance(regionId, url, bucketName, System.currentTimeMillis());
    } finally {
      APITrace.end();
    }
  }
  @Override
  public @Nonnull Iterable<ResourceStatus> listDistributionStatus()
      throws InternalException, CloudException {
    APITrace.begin(provider, "CDN.listDistributionStatus");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new InternalException("No context exists for this request");
      }
      ArrayList<ResourceStatus> distributions = new ArrayList<ResourceStatus>();
      NovaMethod method = new NovaMethod(provider);
      String[] list = method.getItemList(SERVICE, RESOURCE, false);

      if (list != null) {
        for (String container : list) {
          ResourceStatus d = toStatus(container);

          if (d != null) {
            distributions.add(d);
          }
        }
      }
      return distributions;
    } finally {
      APITrace.end();
    }
  }
 @Override
 public boolean exists(@Nonnull String bucketName) throws InternalException, CloudException {
   APITrace.begin(getProvider(), "Blob.exists");
   try {
     return getBucket(bucketName) != null;
   } finally {
     APITrace.end();
   }
 }
 protected void put(@Nullable String bucket, @Nonnull String object, @Nonnull String content)
     throws CloudException, InternalException {
   APITrace.begin(getProvider(), "Blob.putString");
   try {
     // TODO: upload
   } finally {
     APITrace.end();
   }
 }
  @Override
  public @Nonnull String createDnsZone(
      @Nonnull String domainName, @Nonnull String name, @Nonnull String description)
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.createDnsZone");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        logger.error("No context exists for this request");
        throw new InternalException("No context exists for this request");
      }
      NovaMethod method = new NovaMethod(provider);

      HashMap<String, Object> wrapper = new HashMap<String, Object>();
      ArrayList<Map<String, Object>> domains = new ArrayList<Map<String, Object>>();

      HashMap<String, Object> domain = new HashMap<String, Object>();

      domain.put("name", domainName);
      domain.put("comment", description);
      domain.put("emailAddress", "postmaster@" + domainName);

      domains.add(domain);

      wrapper.put("domains", domains);

      JSONObject response =
          method.postString(SERVICE, RESOURCE, null, new JSONObject(wrapper), false);

      try {
        if (response != null && response.has("jobId")) {
          response = waitForJob(response.getString("jobId"));
          if (response != null && response.has("domains")) {
            JSONArray list = response.getJSONArray("domains");

            for (int i = 0; i < list.length(); i++) {
              DNSZone zone = toZone(ctx, list.getJSONObject(i));

              if (zone != null) {
                return zone.getProviderDnsZoneId();
              }
            }
          }
        }
      } catch (JSONException e) {
        logger.error("createDnsZone(): JSON error parsing response: " + e.getMessage());
        e.printStackTrace();
        throw new CloudException(
            CloudErrorType.COMMUNICATION, 200, "invalidResponse", "JSON error parsing " + response);
      }
      logger.error("createDnsZone(): No zone was created, but no error specified");
      throw new CloudException("No zone was created, but no error specified");
    } finally {
      APITrace.end();
    }
  }
 @Override
 public boolean isSubscribed() throws CloudException, InternalException {
   APITrace.begin(provider, "DNS.isSubscribed");
   try {
     return (provider.getCloudName().contains("Rackspace")
         && provider.getAuthenticationContext().getServiceUrl(SERVICE) != null);
   } finally {
     APITrace.end();
   }
 }
  @Override
  public @Nonnull Iterable<ResourceStatus> listDnsZoneStatus()
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.listDnsZoneStatus");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new InternalException("No context exists for this request");
      }
      NovaMethod method = new NovaMethod(provider);
      JSONObject response = method.getResource(SERVICE, RESOURCE, null, false);

      if (response == null) {
        return Collections.emptyList();
      }
      ArrayList<ResourceStatus> zones = new ArrayList<ResourceStatus>();

      try {
        int count = 0, total = 0;

        if (response.has("totalEntries")) {
          total = response.getInt("totalEntries");
        }
        while (response != null) {
          int current = 0;

          if (response.has("domains")) {
            JSONArray list = response.getJSONArray("domains");

            current = list.length();
            count += current;
            for (int i = 0; i < list.length(); i++) {
              JSONObject item = list.getJSONObject(i);

              if (item != null && item.has("id")) {
                zones.add(new ResourceStatus(item.getString("id"), true));
              }
            }
          }
          response = null;
          if (current > 0 && count < total) {
            response = method.getResource(SERVICE, RESOURCE, "?offset=" + count, false);
          }
        }
      } catch (JSONException e) {
        throw new CloudException(
            CloudErrorType.COMMUNICATION, 200, "invalidResponse", "JSON error parsing " + response);
      }
      return zones;
    } finally {
      APITrace.end();
    }
  }
  private @Nonnull Iterable<VPNGateway> listGateways(
      @Nullable String gatewayId, @Nullable String bgpAsn)
      throws CloudException, InternalException {
    APITrace.begin(provider, "listVPCGateways");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(
              provider.getContext(), ELBMethod.DESCRIBE_CUSTOMER_GATEWAYS);
      EC2Method method;
      NodeList blocks;
      Document doc;

      if (gatewayId != null) {
        parameters.put("Filter.1.Name", "customer-gateway-id");
        parameters.put("Filter.1.Value.1", gatewayId);
      } else if (bgpAsn != null) {
        parameters.put("Filter.1.Name", "bgp-asn");
        parameters.put("Filter.1.Value.1", bgpAsn);
      }
      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        doc = method.invoke();
      } catch (EC2Exception e) {
        String code = e.getCode();

        if (code != null) {
          if (code.startsWith("InvalidCustomer") || code.startsWith("InvalidB")) {
            return Collections.emptyList();
          }
        }
        logger.error(e.getSummary());
        throw new CloudException(e);
      }
      ArrayList<VPNGateway> list = new ArrayList<VPNGateway>();

      blocks = doc.getElementsByTagName("item");
      for (int i = 0; i < blocks.getLength(); i++) {
        Node item = blocks.item(i);
        VPNGateway gw = toGateway(ctx, item);

        if (gw != null) {
          list.add(gw);
        }
      }
      return list;
    } finally {
      APITrace.end();
    }
  }
  @Override
  public DNSZone getDnsZone(@Nonnull String providerDnsZoneId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.getDnsZone");
    try {
      CompleteDNS dns = getCompleteDNS(providerDnsZoneId, false);

      return (dns == null ? null : dns.domain);
    } finally {
      APITrace.end();
    }
  }
 @Nonnull
 @Override
 public FileTransfer getDownloadRequestResult(
     @Nonnull String bucket, @Nonnull String requestId, @Nonnull File toFile)
     throws InternalException, CloudException {
   APITrace.begin(getProvider(), "Blob.getDownloadRequestResult");
   try {
     throw new OperationNotSupportedException("glacier downloads are not yet supported");
   } finally {
     APITrace.end();
   }
 }
 @Nonnull
 @Override
 public OfflineStoreRequest createDownloadRequest(@Nonnull String bucket, @Nonnull String object)
     throws CloudException, InternalException {
   APITrace.begin(getProvider(), "Blob.createDownloadRequest");
   try {
     // todo
     throw new OperationNotSupportedException("glacier downloads are not yet supported");
   } finally {
     APITrace.end();
   }
 }
  @Override
  public @Nullable VPN getVPN(@Nonnull String providerVpnId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "getVPN");
    try {
      Iterator<VPN> it = listVPNs(providerVpnId).iterator();

      return (it.hasNext() ? it.next() : null);
    } finally {
      APITrace.end();
    }
  }
  private @Nonnull List<String> lookupRecord(DNSRecord record)
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.lookupRecord");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        logger.error("No context exists for this request");
        throw new InternalException("No context exists for this request");
      }
      NovaMethod method = new NovaMethod(provider);
      JSONObject response =
          method.getResource(SERVICE, RESOURCE, record.getProviderZoneId() + "/records", false);

      if (response == null) {
        return null;
      }
      ArrayList<String> ids = new ArrayList<String>();

      try {
        if (response.has("records")) {
          JSONArray list = response.getJSONArray("records");

          for (int i = 0; i < list.length(); i++) {
            JSONObject item = list.getJSONObject(i);

            if (item != null) {
              String n = (item.has("name") ? item.getString("name") : null);
              String id = (item.has("id") ? item.getString("id") : null);

              if (n == null || id == null) {
                continue;
              }
              if (record.getName().equals(n) || record.getName().equals(n + ".")) {
                ids.add(id);
              }
            }
          }
        }
      } catch (JSONException e) {
        logger.error("lookupRecord(): JSON error parsing response: " + e.getMessage());
        e.printStackTrace();
        throw new CloudException(
            CloudErrorType.COMMUNICATION, 200, "invalidResponse", "JSON error parsing " + response);
      }
      return ids;
    } finally {
      APITrace.end();
    }
  }
  @Override
  public @Nonnull VPNGateway createVPNGateway(
      @Nonnull String endpoint,
      @Nonnull String name,
      @Nonnull String description,
      @Nonnull VPNProtocol protocol,
      @Nonnull String bgpAsn)
      throws CloudException, InternalException {
    APITrace.begin(provider, "createVPNGateway");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(provider.getContext(), ELBMethod.CREATE_CUSTOMER_GATEWAY);
      EC2Method method;
      NodeList blocks;
      Document doc;

      parameters.put("Type", getAWSProtocol(protocol));
      parameters.put("IpAddress", endpoint);
      parameters.put("BgpAsn", bgpAsn);
      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        doc = method.invoke();
      } catch (EC2Exception e) {
        logger.error(e.getSummary());
        if (logger.isDebugEnabled()) {
          e.printStackTrace();
        }
        throw new CloudException(e);
      }
      blocks = doc.getElementsByTagName("customerGateway");

      for (int i = 0; i < blocks.getLength(); i++) {
        Node item = blocks.item(i);
        VPNGateway gateway = toGateway(ctx, item);

        if (gateway != null) {
          return gateway;
        }
      }
      throw new CloudException("No VPN gateway was created, but no error was reported");
    } finally {
      APITrace.end();
    }
  }
  @Override
  public void disconnectFromGateway(@Nonnull String vpnId, @Nonnull String gatewayId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "disconnectVPNFromGateway");
    try {
      VPNGateway gateway = getGateway(gatewayId);
      VPN vpn = getVPN(vpnId);

      if (gateway == null) {
        throw new CloudException("No such VPN gateway: " + gatewayId);
      }
      if (vpn == null) {
        throw new CloudException("No such VPN: " + vpnId);
      }
      String connectionId = null;

      for (VPNConnection c : listConnections(vpnId, null)) {
        if (gatewayId.equals(c.getProviderGatewayId())) {
          connectionId = c.getProviderVpnConnectionId();
          break;
        }
      }
      if (connectionId == null) {
        logger.warn(
            "Attempt to disconnect a VPN from a gateway when there was no connection in the cloud");
        return;
      }
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(provider.getContext(), ELBMethod.DELETE_VPN_CONNECTION);
      EC2Method method;

      parameters.put("VpnConnectionId", connectionId);
      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        method.invoke();
      } catch (EC2Exception e) {
        logger.error(e.getSummary());
        e.printStackTrace();
        throw new CloudException(e);
      }
    } finally {
      APITrace.end();
    }
  }
 private void list(
     @Nonnull String regionId, @Nullable String bucket, @Nonnull Jiterator<Blob> iterator)
     throws CloudException, InternalException {
   APITrace.begin(getProvider(), "Blob.list");
   try {
     if (bucket == null) {
       loadVaults(regionId, iterator);
     } else {
       throw new OperationNotSupportedException(
           "Glacier vault contents cannot be listed synchronously");
     }
   } finally {
     APITrace.end();
   }
 }
  @Override
  public @Nullable VPNGateway getGateway(@Nonnull String gatewayId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "getGateway");
    try {
      Iterator<VPNGateway> it = listGateways(gatewayId, null).iterator();

      if (it.hasNext()) {
        return it.next();
      }
      return null;
    } finally {
      APITrace.end();
    }
  }
  private @Nonnull Iterable<VPN> listVPNs(@Nullable String vpnId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "listVPNs");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(provider.getContext(), ELBMethod.DESCRIBE_VPN_GATEWAYS);
      EC2Method method;
      NodeList blocks;
      Document doc;

      if (vpnId != null) {
        parameters.put("VpnGatewayId.1", vpnId);
      }
      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        doc = method.invoke();
      } catch (EC2Exception e) {
        String code = e.getCode();

        if (code != null) {
          if (code.startsWith("InvalidVpn")) {
            return Collections.emptyList();
          }
        }
        logger.error(e.getSummary());
        throw new CloudException(e);
      }
      ArrayList<VPN> list = new ArrayList<VPN>();

      blocks = doc.getElementsByTagName("item");
      for (int i = 0; i < blocks.getLength(); i++) {
        Node item = blocks.item(i);
        VPN vpn = toVPN(ctx, item);

        if (vpn != null) {
          list.add(vpn);
        }
      }
      return list;
    } finally {
      APITrace.end();
    }
  }
  @Override
  public void delete(@Nonnull String distributionId) throws InternalException, CloudException {
    APITrace.begin(provider, "CDN.delete");
    try {
      HashMap<String, String> customHeaders = new HashMap<String, String>();

      customHeaders.put("X-Log-Retention", "True");
      customHeaders.put("X-CDN-Enabled", "False");

      NovaMethod method = new NovaMethod(provider);

      method.putResourceHeaders(SERVICE, RESOURCE, distributionId, customHeaders);
    } finally {
      APITrace.end();
    }
  }
  @Override
  public @Nonnull Iterable<ResourceStatus> listGatewayStatus()
      throws CloudException, InternalException {
    APITrace.begin(provider, "listVPCGatewayStatus");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(
              provider.getContext(), ELBMethod.DESCRIBE_CUSTOMER_GATEWAYS);
      EC2Method method;
      NodeList blocks;
      Document doc;

      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        doc = method.invoke();
      } catch (EC2Exception e) {
        String code = e.getCode();

        if (code != null) {
          if (code.startsWith("InvalidCustomer") || code.startsWith("InvalidB")) {
            return Collections.emptyList();
          }
        }
        logger.error(e.getSummary());
        throw new CloudException(e);
      }
      ArrayList<ResourceStatus> list = new ArrayList<ResourceStatus>();

      blocks = doc.getElementsByTagName("item");
      for (int i = 0; i < blocks.getLength(); i++) {
        ResourceStatus status = toGatewayStatus(blocks.item(i));

        if (status != null) {
          list.add(status);
        }
      }
      return list;
    } finally {
      APITrace.end();
    }
  }
  @Override
  public void removeBucket(@Nonnull String bucket) throws CloudException, InternalException {
    APITrace.begin(getProvider(), "Blob.removeBucket");
    try {
      String regionId = getContext().getRegionId();

      if (regionId == null) {
        throw new CloudException("No region was set for this request");
      }

      GlacierMethod method =
          GlacierMethod.build(getProvider(), GlacierAction.DELETE_VAULT).vaultId(bucket).toMethod();
      method.invoke();
    } finally {
      APITrace.end();
    }
  }
  private void listRequests(
      @Nonnull String bucket, @Nonnull Jiterator<OfflineStoreRequest> iterator)
      throws CloudException, InternalException {
    APITrace.begin(getProvider(), "Blob.listRequests");
    try {

      boolean needQuery = true;
      String marker = null;
      Map<String, String> queryParameters = new HashMap<String, String>(1);

      // glacier can paginate results. it returns a "marker" string in the JSON response
      // which indicates you should make another query. The marker string should be passed
      // as a query parameter in the next query.

      while (needQuery) {
        if (marker != null) {
          queryParameters.put("marker", marker);
        }

        GlacierMethod method =
            GlacierMethod.build(getProvider(), GlacierAction.LIST_JOBS)
                .vaultId(bucket)
                .queryParameters(queryParameters)
                .toMethod();
        final JSONObject jsonObject = method.invokeJson();

        try {
          JSONArray vaultList = jsonObject.getJSONArray("JobList");

          for (int i = 0; i < vaultList.length(); i++) {
            iterator.push(loadRequestJson(vaultList.getJSONObject(i), bucket));
          }

          marker = getPaginationMarker(jsonObject);
          if (marker == null) {
            needQuery = false;
          }
        } catch (JSONException e) {
          throw new CloudException(e);
        }
      }
    } finally {
      APITrace.end();
    }
  }
  @Override
  public void connectToGateway(@Nonnull String providerVpnId, @Nonnull String toGatewayId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "connectVPNToGateway");
    try {
      VPNGateway gateway = getGateway(toGatewayId);
      VPN vpn = getVPN(providerVpnId);

      if (gateway == null) {
        throw new CloudException("No such VPN gateway: " + toGatewayId);
      }
      if (vpn == null) {
        throw new CloudException("No such VPN: " + providerVpnId);
      }
      if (!gateway.getProtocol().equals(vpn.getProtocol())) {
        throw new CloudException(
            "VPN protocol mismatch between VPN and gateway: "
                + vpn.getProtocol()
                + " vs "
                + gateway.getProtocol());
      }
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(provider.getContext(), ELBMethod.CREATE_VPN_CONNECTION);
      EC2Method method;

      parameters.put("Type", getAWSProtocol(vpn.getProtocol()));
      parameters.put("CustomerGatewayId", gateway.getProviderVpnGatewayId());
      parameters.put("VpnGatewayId", vpn.getProviderVpnId());
      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        method.invoke();
      } catch (EC2Exception e) {
        logger.error(e.getSummary());
        e.printStackTrace();
        throw new CloudException(e);
      }
    } finally {
      APITrace.end();
    }
  }
  @Override
  public @Nonnull String create(
      @Nonnull String origin, @Nonnull String name, boolean active, @Nullable String... aliases)
      throws InternalException, CloudException {
    APITrace.begin(provider, "CDN.create");
    try {
      HashMap<String, String> customHeaders = new HashMap<String, String>();

      customHeaders.put("X-Log-Retention", "True");
      customHeaders.put("X-CDN-Enabled", "True");
      NovaMethod method = new NovaMethod(provider);

      method.putResourceHeaders(SERVICE, RESOURCE, origin, customHeaders);
      return origin;
    } finally {
      APITrace.end();
    }
  }
 @Override
 public @Nonnull Blob upload(
     @Nonnull File source, @Nullable String bucket, @Nonnull String fileName)
     throws CloudException, InternalException {
   APITrace.begin(getProvider(), "Blob.upload");
   try {
     if (bucket == null) {
       throw new OperationNotSupportedException("Root objects are not supported");
     }
     if (!exists(bucket)) {
       createBucket(bucket, false);
     }
     put(bucket, fileName, source);
     return getObject(bucket, fileName);
   } finally {
     APITrace.end();
   }
 }
  @Override
  public void deleteDnsZone(@Nonnull String providerDnsZoneId)
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.deleteDnsZone");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        logger.error("No context exists for this request");
        throw new InternalException("No context exists for this request");
      }
      NovaMethod method = new NovaMethod(provider);

      method.deleteResource(SERVICE, RESOURCE, providerDnsZoneId, null);
    } finally {
      APITrace.end();
    }
  }
  @Override
  public @Nonnull VPN createVPN(
      @Nullable String dataCenterId,
      @Nonnull String name,
      @Nonnull String description,
      @Nonnull VPNProtocol protocol)
      throws CloudException, InternalException {
    APITrace.begin(provider, "createVPN");
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        throw new CloudException("No context was configured");
      }
      Map<String, String> parameters =
          provider.getStandardParameters(provider.getContext(), ELBMethod.CREATE_VPN_GATEWAY);
      EC2Method method;
      NodeList blocks;
      Document doc;

      parameters.put("Type", getAWSProtocol(protocol));
      method = new EC2Method(provider, provider.getEc2Url(), parameters);
      try {
        doc = method.invoke();
      } catch (EC2Exception e) {
        logger.error(e.getSummary());
        e.printStackTrace();
        throw new CloudException(e);
      }
      blocks = doc.getElementsByTagName("vpnGateway");

      for (int i = 0; i < blocks.getLength(); i++) {
        Node item = blocks.item(i);
        VPN vpn = toVPN(ctx, item);

        if (vpn != null) {
          return vpn;
        }
      }
      throw new CloudException("No VPN was created, but no error was reported");
    } finally {
      APITrace.end();
    }
  }