@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();
    }
  }
  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);
    }
  }
  private void listSubdomains(
      @Nonnull ProviderContext ctx,
      @Nonnull List<DNSZone> intoList,
      @Nonnull DNSZone parent,
      @Nonnull JSONArray subdomains)
      throws CloudException, InternalException {
    try {
      for (int i = 0; i < subdomains.length(); i++) {
        DNSZone z = toZone(ctx, subdomains.getJSONObject(i));

        if (z != null) {
          z.setNameservers(parent.getNameservers());
          intoList.add(z);
        }
      }
    } catch (JSONException e) {
      throw new CloudException(e);
    }
  }
  private @Nullable DNSZone toZone(@Nonnull ProviderContext ctx, @Nullable JSONObject json)
      throws CloudException, InternalException {
    if (json == null) {
      return null;
    }
    try {
      String zoneId = (json.has("id") ? json.getString("id") : null);

      if (zoneId == null) {
        return null;
      }

      String name = (json.has("name") ? json.getString("name") : null);

      if (name == null) {
        return null;
      }

      String description = (json.has("comment") ? json.getString("comment") : null);

      if (description == null) {
        description = name;
      }
      JSONArray nameservers = (json.has("nameservers") ? json.getJSONArray("nameservers") : null);

      DNSZone zone = new DNSZone();

      zone.setDescription(description);
      zone.setDomainName(name);
      zone.setName(name);
      zone.setProviderDnsZoneId(zoneId);
      zone.setProviderOwnerId(getTenantId());
      if (nameservers != null) {
        String[] ns = new String[nameservers.length()];

        for (int i = 0; i < nameservers.length(); i++) {
          JSONObject ob = nameservers.getJSONObject(i);

          if (ob.has("name")) {
            ns[i] = ob.getString("name");
          }
        }
        zone.setNameservers(ns);
      } else {
        zone.setNameservers(new String[0]);
      }
      return zone;
    } catch (JSONException e) {
      throw new CloudException(e);
    }
  }
  @Override
  public @Nonnull DNSRecord addDnsRecord(
      @Nonnull String providerDnsZoneId,
      @Nonnull DNSRecordType recordType,
      @Nonnull String name,
      @Nonnegative int ttl,
      @Nonnull String... values)
      throws CloudException, InternalException {
    APITrace.begin(provider, "DNS.addDnsRecord");
    try {
      DNSZone zone = getDnsZone(providerDnsZoneId);

      if (zone == null) {
        throw new CloudException("No such zone: " + providerDnsZoneId);
      }
      if (recordType.equals(DNSRecordType.A)
          || recordType.equals(DNSRecordType.AAAA)
          || recordType.equals(DNSRecordType.CNAME)
          || recordType.equals(DNSRecordType.MX)) {
        if (name.endsWith(zone.getDomainName() + ".")) {
          name = name.substring(0, name.length() - 1);
        } else if (!name.endsWith(zone.getDomainName())) {
          name = name + "." + zone.getDomainName();
        }
      }
      ProviderContext ctx = provider.getContext();

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

      for (String value : values) {
        if (value != null) {
          NovaMethod method = new NovaMethod(provider);

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

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

          record.put("name", name);
          record.put("data", value);
          record.put("type", recordType.name());
          record.put("ttl", ttl > 0 ? ttl : 3600);

          records.add(record);

          wrapper.put("records", records);

          JSONObject response =
              method.postString(
                  SERVICE,
                  RESOURCE,
                  providerDnsZoneId + "/records",
                  new JSONObject(wrapper),
                  false);

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

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

                  if (r != null) {
                    lastRecord = r;
                  }
                }
              }
            }
          } 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);
          }
        }
      }
      if (lastRecord == null) {
        logger.error("addDnsRecord(): No record was created, but no error specified");
        throw new CloudException("No record was created, but no error specified");
      }
      return lastRecord;
    } finally {
      APITrace.end();
    }
  }