@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();
    }
  }
  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();
    }
  }
  private @Nullable DNSRecord toRecord(
      @SuppressWarnings("UnusedParameters") @Nonnull ProviderContext ctx,
      @Nonnull DNSZone zone,
      @Nullable JSONObject json)
      throws CloudException, InternalException {
    if (json == null) {
      return null;
    }
    try {
      String recordId = (json.has("id") ? json.getString("id") : null);

      if (recordId == null) {
        return null;
      }
      String name = (json.has("name") ? json.getString("name") : null);

      if (name == null) {
        return null;
      }
      if (name.endsWith(zone.getDomainName())) {
        name = name + ".";
      }
      DNSRecordType recordType = DNSRecordType.A;
      String type = (json.has("type") ? json.getString("type") : null);

      if (type != null) {
        recordType = DNSRecordType.valueOf(type.toUpperCase());
      }
      String data = (json.has("data") ? json.getString("data") : null);
      int ttl = (json.has("ttl") ? json.getInt("ttl") : 3600);

      DNSRecord record = new DNSRecord();

      record.setName(name);
      record.setProviderZoneId(zone.getProviderDnsZoneId());
      record.setTtl(ttl);
      record.setType(recordType);
      record.setValues(data == null ? new String[0] : new String[] {data});

      return record;
    } catch (JSONException e) {
      throw new CloudException(e);
    }
  }
  @Override
  public @Nonnull Iterable<DNSRecord> listDnsRecords(
      @Nonnull String providerDnsZoneId, @Nullable DNSRecordType forType, @Nullable String name)
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.listDnsRecords");
    try {
      DNSZone zone = getDnsZone(providerDnsZoneId);

      if (zone == null) {
        throw new CloudException("No such zone: " + providerDnsZoneId);
      }
      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, providerDnsZoneId + "/records", false);

      if (response == null) {
        return Collections.emptyList();
      }
      ArrayList<DNSRecord> records = new ArrayList<DNSRecord>();
      try {
        int count = 0, total = 0;

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

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

            current = list.length();
            count += current;
            for (int i = 0; i < list.length(); i++) {
              DNSRecord record = toRecord(ctx, zone, list.getJSONObject(i));

              if (record != null) {
                if (forType == null || forType.equals(record.getType())) {
                  if (name == null || name.equals(record.getName())) {
                    records.add(record);
                  }
                }
              }
            }
          }
          response = null;
          if (current > 0 && count < total) {
            response =
                method.getResource(
                    SERVICE, RESOURCE, providerDnsZoneId + "/records?offset=" + count, false);
          }
        }
      } catch (JSONException e) {
        logger.error("listDnsRecords(): JSON error parsing response: " + e.getMessage());
        e.printStackTrace();
        throw new CloudException(
            CloudErrorType.COMMUNICATION, 200, "invalidResponse", "JSON error parsing " + response);
      }
      return records;
    } finally {
      APITrace.end();
    }
  }