@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 CompleteDNS getCompleteDNS(@Nonnull String providerDnsZoneId, boolean withSubdomains)
      throws CloudException, InternalException {
    Logger std = NovaOpenStack.getLogger(RackspaceCloudDNS.class, "std");

    if (std.isTraceEnabled()) {
      std.trace("ENTER: " + RackspaceCloudDNS.class.getName() + ".getCompleteDNS()");
    }
    try {
      ProviderContext ctx = provider.getContext();

      if (ctx == null) {
        std.error("No context exists for this request");
        throw new InternalException("No context exists for this request");
      }
      String query = providerDnsZoneId + "?showRecords=true";

      if (withSubdomains) {
        query = query + "&showSubdomains=true";
      }
      NovaMethod method = new NovaMethod(provider);
      JSONObject response = method.getResource(SERVICE, RESOURCE, query, false);

      if (response == null) {
        return null;
      }
      try {
        DNSZone zone = toZone(ctx, response);

        if (zone != null) {
          CompleteDNS dns = new CompleteDNS();

          dns.domain = zone;
          dns.subdomains = new ArrayList<DNSZone>();

          JSONObject subdomains =
              (response.has("subdomains") ? response.getJSONObject("subdomains") : null);

          if (subdomains != null) {
            JSONArray domains =
                (subdomains.has("domains") ? subdomains.getJSONArray("domains") : null);

            if (domains != null) {
              listSubdomains(ctx, dns.subdomains, zone, domains);
            }
          }
          return dns;
        }
      } catch (JSONException e) {
        std.error("getCompleteDNS(): JSON error parsing response: " + e.getMessage());
        e.printStackTrace();
        throw new CloudException(
            CloudErrorType.COMMUNICATION, 200, "invalidResponse", "JSON error parsing " + response);
      }
      return null;
    } finally {
      if (std.isTraceEnabled()) {
        std.trace("exit - " + RackspaceCloudDNS.class.getName() + ".getCompleteDNS()");
      }
    }
  }
  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 JSONObject waitForJob(String jobId) throws CloudException, InternalException {
    long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 20);

    ProviderContext ctx = provider.getContext();

    if (ctx == null) {
      throw new InternalException("No context exists for this request");
    }
    while (System.currentTimeMillis() < timeout) {
      try {
        NovaMethod method = new NovaMethod(provider);
        JSONObject response =
            method.getResource(SERVICE, "/status", jobId + "?showDetails=true", false);

        if (response == null) {
          throw new CloudException("Job disappeared");
        }
        String status = (response.has("status") ? response.getString("status") : null);

        if (status == null) {
          throw new CloudException("No job status");
        }
        if (status.equalsIgnoreCase("completed")) {
          if (response.has("response")) {
            return response.getJSONObject("response");
          }
        } else if (status.equalsIgnoreCase("error")) {
          if (response.has("error")) {
            JSONObject error = response.getJSONObject("error");

            if (error == null) {
              throw new CloudException("Unknown error");
            }
            int code = (error.has("code") ? error.getInt("code") : 418);

            throw new NovaException(NovaException.parseException(code, error.toString()));
          }
          throw new CloudException("Unknown error");
        }
      } catch (JSONException e) {
        throw new CloudException("Invalid JSON from server: " + e.getMessage());
      }
      try {
        Thread.sleep(CalendarWrapper.SECOND * 30);
      } catch (InterruptedException ignore) {
      }
    }
    throw new CloudException("Operation timed out");
  }
  @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();
    }
  }