Merge pull request #1523 from jclouds/ultradns-directional-readonly-1.6.x

Ultradns directional readonly 1.6.x
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java
index 827038d..c302348 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java
@@ -19,6 +19,7 @@
 package org.jclouds.ultradns.ws;
 
 import java.io.Closeable;
+import java.util.Map;
 
 import javax.inject.Named;
 import javax.ws.rs.POST;
@@ -30,6 +31,9 @@
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
 import org.jclouds.ultradns.ws.domain.Account;
+import org.jclouds.ultradns.ws.domain.Region;
+import org.jclouds.ultradns.ws.features.DirectionalGroupApi;
+import org.jclouds.ultradns.ws.features.DirectionalPoolApi;
 import org.jclouds.ultradns.ws.features.ResourceRecordApi;
 import org.jclouds.ultradns.ws.features.RoundRobinPoolApi;
 import org.jclouds.ultradns.ws.features.TaskApi;
@@ -37,6 +41,7 @@
 import org.jclouds.ultradns.ws.features.ZoneApi;
 import org.jclouds.ultradns.ws.filters.SOAPWrapWithPasswordAuth;
 import org.jclouds.ultradns.ws.xml.AccountHandler;
+import org.jclouds.ultradns.ws.xml.RegionListHandler;
 
 /**
  * Provides access to Neustar UltraDNS via the SOAP API
@@ -58,13 +63,22 @@
    Account getCurrentAccount();
 
    /**
-    * Provides synchronous access to Zone features.
+    * Lists the directional regions available in the account.
+    */
+   @Named("getAvailableRegions")
+   @POST
+   @XMLResponseParser(RegionListHandler.class)
+   @Payload("<v01:getAvailableRegions/>")
+   Map<Integer, Region> getRegionsById();
+
+   /**
+    * Provides access to Zone features.
     */
    @Delegate
    ZoneApi getZoneApi();
 
    /**
-    * Provides synchronous access to Resource Record features.
+    * Provides access to Resource Record features.
     * 
     * @param zoneName
     *           zoneName including a trailing dot
@@ -73,7 +87,7 @@
    ResourceRecordApi getResourceRecordApiForZone(@PayloadParam("zoneName") String zoneName);
 
    /**
-    * Provides synchronous access to Round Robin Pool features.
+    * Provides access to Round Robin Pool features.
     * 
     * @param zoneName
     *           zoneName including a trailing dot
@@ -82,7 +96,7 @@
    RoundRobinPoolApi getRoundRobinPoolApiForZone(@PayloadParam("zoneName") String zoneName);
 
    /**
-    * Provides synchronous access to Traffic Controller Pool features.
+    * Provides access to Traffic Controller Pool features.
     * 
     * @param zoneName
     *           zoneName including a trailing dot
@@ -91,7 +105,25 @@
    TrafficControllerPoolApi getTrafficControllerPoolApiForZone(@PayloadParam("zoneName") String zoneName);
 
    /**
-    * Provides synchronous access to Task features.
+    * Provides access to Account-Level Directional Group features.
+    * 
+    * @param accountId
+    *           id of the account where the groups live.
+    */
+   @Delegate
+   DirectionalGroupApi getDirectionalGroupApiForAccount(@PayloadParam("accountId") String accountId);
+
+   /**
+    * Provides access to Directional Pool features.
+    * 
+    * @param zoneName
+    *           zoneName including a trailing dot
+    */
+   @Delegate
+   DirectionalPoolApi getDirectionalPoolApiForZone(@PayloadParam("zoneName") String zoneName);
+
+   /**
+    * Provides access to Task features.
     */
    @Delegate
    TaskApi getTaskApi();
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/binders/DirectionalGroupCoordinatesToXML.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/binders/DirectionalGroupCoordinatesToXML.java
new file mode 100644
index 0000000..729fee5
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/binders/DirectionalGroupCoordinatesToXML.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.binders;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.UriTemplates;
+import org.jclouds.rest.Binder;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupCoordinates;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class DirectionalGroupCoordinatesToXML implements Binder {
+   private static final String TEMPLATE = "<v01:getDirectionalDNSRecordsForGroup><groupName>{groupName}</groupName><hostName>{recordName}</hostName><zoneName>{zoneName}</zoneName><poolRecordType>{recordType}</poolRecordType></v01:getDirectionalDNSRecordsForGroup>";
+
+   @SuppressWarnings("unchecked")
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object in) {
+      DirectionalGroupCoordinates group = DirectionalGroupCoordinates.class.cast(in);
+      ImmutableMap<String, Object> variables = ImmutableMap.<String, Object> builder()
+                                                           .put("zoneName", group.getZoneName())
+                                                           .put("recordName", group.getRecordName())
+                                                           .put("recordType", group.getRecordType())
+                                                           .put("groupName", group.getGroupName()).build();
+      return (R) request.toBuilder().payload(UriTemplates.expand(TEMPLATE, variables)).build();
+   }
+}
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/config/UltraDNSWSHttpApiModule.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/config/UltraDNSWSHttpApiModule.java
index 9c23387..1a4d83e 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/config/UltraDNSWSHttpApiModule.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/config/UltraDNSWSHttpApiModule.java
@@ -28,6 +28,7 @@
 import org.jclouds.ultradns.ws.UltraDNSWSApi;
 import org.jclouds.ultradns.ws.handlers.UltraDNSWSErrorHandler;
 
+
 /**
  * Configures the UltraDNSWS connection.
  * 
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/AccountLevelGroup.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/AccountLevelGroup.java
new file mode 100644
index 0000000..8776c6d
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/AccountLevelGroup.java
@@ -0,0 +1,141 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.ultradns.ws.domain.DirectionalPool.Type;
+
+import com.google.common.base.Objects;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public final class AccountLevelGroup {
+
+   private final String id;
+   private final String name;
+   private final Type type;
+   private final int recordCount;
+
+   private AccountLevelGroup(String id, String name, Type type, int recordCount) {
+      this.id = checkNotNull(id, "id");
+      this.name = checkNotNull(name, "name of %s", id);
+      this.type = checkNotNull(type, "type of %s", id);
+      this.recordCount = recordCount;
+      checkArgument(recordCount >= 0, "recordCount of %s must be >= 0", id);
+   }
+
+   public String getId() {
+      return id;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public Type getType() {
+      return type;
+   }
+
+   public int getRecordCount() {
+      return recordCount;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(id, name, type);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null)
+         return false;
+      if (getClass() != obj.getClass())
+         return false;
+      AccountLevelGroup that = AccountLevelGroup.class.cast(obj);
+      return Objects.equal(this.id, that.id) && Objects.equal(this.name, that.name)
+            && Objects.equal(this.type, that.type);
+   }
+
+   @Override
+   public String toString() {
+      return Objects.toStringHelper(this).omitNullValues().add("id", id).add("name", name).add("type", type)
+            .add("recordCount", recordCount).toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().from(this);
+   }
+
+   public final static class Builder {
+      private String id;
+      private String name;
+      private Type type;
+      private int recordCount = -1;
+
+      /**
+       * @see AccountLevelGroup#getId()
+       */
+      public Builder id(String id) {
+         this.id = id;
+         return this;
+      }
+
+      /**
+       * @see AccountLevelGroup#getName()
+       */
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      /**
+       * @see AccountLevelGroup#getType()
+       */
+      public Builder type(Type type) {
+         this.type = type;
+         return this;
+      }
+
+      /**
+       * @see AccountLevelGroup#getRecordCount()
+       */
+      public Builder recordCount(int recordCount) {
+         this.recordCount = recordCount;
+         return this;
+      }
+
+      public AccountLevelGroup build() {
+         return new AccountLevelGroup(id, name, type, recordCount);
+      }
+
+      public Builder from(AccountLevelGroup in) {
+         return this.id(in.id).name(in.name).type(in.type).recordCount(in.recordCount);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroup.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroup.java
new file mode 100644
index 0000000..7caaf52
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroup.java
@@ -0,0 +1,103 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+
+/**
+ * @author Adrian Cole
+ */
+public class DirectionalGroup {
+   private final String id;
+   private final String name;
+
+   private DirectionalGroup(String id, String name) {
+      this.id = checkNotNull(id, "id");
+      this.name = checkNotNull(name, "name");
+   }
+
+   public String getId() {
+      return id;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(id, name);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
+      DirectionalGroup that = DirectionalGroup.class.cast(obj);
+      return equal(this.id, that.id) && equal(this.name, that.name);
+   }
+
+   @Override
+   public String toString() {
+      return toStringHelper(this).omitNullValues().add("id", id).add("name", name).toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().from(this);
+   }
+
+   public final static class Builder {
+      private String id;
+      private String name;
+
+      /**
+       * @see DirectionalGroup#getId()
+       */
+      public Builder id(String id) {
+         this.id = id;
+         return this;
+      }
+
+      /**
+       * @see DirectionalGroup#getName()
+       */
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      public DirectionalGroup build() {
+         return new DirectionalGroup(id, name);
+      }
+
+      public Builder from(DirectionalGroup in) {
+         return id(in.id).name(in.name);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroupCoordinates.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroupCoordinates.java
new file mode 100644
index 0000000..ecd6075
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroupCoordinates.java
@@ -0,0 +1,156 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+
+/**
+ * @author Adrian Cole
+ */
+public class DirectionalGroupCoordinates {
+
+   private final String zoneName;
+   private final String recordName;
+   private final int recordType;
+   private final String groupName;
+
+   private DirectionalGroupCoordinates(String zoneName, String recordName, int recordType, String groupName) {
+      this.zoneName = checkNotNull(zoneName, "zoneName");
+      this.recordName = checkNotNull(recordName, "recordName");
+      checkArgument(recordType >= 0, "recordType of %s must be >= 0", recordName);
+      this.recordType = recordType;
+      this.groupName = checkNotNull(groupName, "groupName");
+   }
+
+   /**
+    * the {@link DirectionalRecordDetail#getZoneName() name} of the zone.
+    */
+   public String getZoneName() {
+      return zoneName;
+   }
+
+   /**
+    * the {@link DirectionalRecordDetail#getName() dname} of the record.
+    */
+   public String getRecordName() {
+      return recordName;
+   }
+
+   /**
+    * the {@link DirectionalRecord#getType() recordType} of the record.
+    * 
+    * @see DirectionalRecordDetail#getRecord()
+    */
+   public int getRecordType() {
+      return recordType;
+   }
+
+   /**
+    * the {@link DirectionalGroup#getName() name} of the directional group.
+    * 
+    * @see DirectionalRecordDetail#getGroup()
+    * @see DirectionalRecordDetail#getGeolocationGroup()
+    * @see DirectionalRecordDetail#getSourceIpGroup()
+    */
+   public String getGroupName() {
+      return groupName;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(zoneName, recordName, recordType, groupName);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
+      DirectionalGroupCoordinates that = DirectionalGroupCoordinates.class.cast(obj);
+      return equal(this.zoneName, that.zoneName) && equal(this.recordName, that.recordName)
+            && equal(this.recordType, that.recordType) && equal(this.groupName, that.groupName);
+   }
+
+   @Override
+   public String toString() {
+      return toStringHelper(this).add("zoneName", zoneName).add("recordName", recordName).add("recordType", recordType)
+            .add("groupName", groupName).toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().from(this);
+   }
+
+   public final static class Builder {
+      private String zoneName;
+      private String recordName;
+      private int recordType = -1;
+      private String groupName;
+
+      /**
+       * @see DirectionalGroupCoordinates#getZoneName()
+       */
+      public Builder zoneName(String zoneName) {
+         this.zoneName = zoneName;
+         return this;
+      }
+
+      /**
+       * @see DirectionalGroupCoordinates#getRecordName()
+       */
+      public Builder recordName(String recordName) {
+         this.recordName = recordName;
+         return this;
+      }
+
+      /**
+       * @see DirectionalGroupCoordinates#getRecordType()
+       */
+      public Builder recordType(int recordType) {
+         this.recordType = recordType;
+         return this;
+      }
+
+      /**
+       * @see DirectionalGroupCoordinates#getGroupName()
+       */
+      public Builder groupName(String groupName) {
+         this.groupName = groupName;
+         return this;
+      }
+
+      public DirectionalGroupCoordinates build() {
+         return new DirectionalGroupCoordinates(zoneName, recordName, recordType, groupName);
+      }
+
+      public Builder from(DirectionalGroupCoordinates in) {
+         return zoneName(in.zoneName).recordName(in.zoneName).recordType(in.recordType).groupName(in.groupName);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroupNameAndRegions.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroupNameAndRegions.java
new file mode 100644
index 0000000..9c7b55f
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalGroupNameAndRegions.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Set;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ForwardingSet;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * A region is a set of territory names.
+ * 
+ * @author Adrian Cole
+ */
+public class DirectionalGroupNameAndRegions extends ForwardingSet<Region> {
+
+   private final String name;
+   private final Set<Region> regions;
+
+   private DirectionalGroupNameAndRegions(String name, Set<Region> regions) {
+      this.name = checkNotNull(name, "name");
+      this.regions = checkNotNull(regions, "regions of %s", name);
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public Set<Region> getRegions() {
+      return regions;
+   }
+
+   @Override
+   protected Set<Region> delegate() {
+      return regions;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(name, regions);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
+      DirectionalGroupNameAndRegions that = DirectionalGroupNameAndRegions.class.cast(obj);
+      return equal(this.name, that.name) && equal(this.regions, that.regions);
+   }
+
+   @Override
+   public String toString() {
+      return toStringHelper(this).add("name", name).add("regions", regions).toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().from(this);
+   }
+
+   public final static class Builder {
+      private String name;
+      private ImmutableSet.Builder<Region> regions = ImmutableSet.<Region> builder();
+
+      /**
+       * @see DirectionalGroupNameAndRegions#getName()
+       */
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      /**
+       * adds to current regions
+       * 
+       * @see DirectionalGroupNameAndRegions#getRegions()
+       */
+      public Builder addRegion(Region region) {
+         this.regions.add(region);
+         return this;
+      }
+
+      /**
+       * replaces current regions
+       * 
+       * @see DirectionalGroupNameAndRegions#getRegions()
+       */
+      public Builder regions(Iterable<Region> regions) {
+         this.regions = ImmutableSet.<Region> builder().addAll(regions);
+         return this;
+      }
+
+      public DirectionalGroupNameAndRegions build() {
+         return new DirectionalGroupNameAndRegions(name, regions.build());
+      }
+
+      public Builder from(DirectionalGroupNameAndRegions in) {
+         return name(in.getName()).regions(in.getRegions());
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalPool.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalPool.java
new file mode 100644
index 0000000..7941e14
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalPool.java
@@ -0,0 +1,190 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+
+/**
+ * Records are resolved in consideration of the location of the requestor.
+ * 
+ * @author Adrian Cole
+ */
+public final class DirectionalPool {
+
+   private final String zoneId;
+   private final String id;
+   private final Optional<String> description;
+   private final String dname;
+   private final Type type;
+   private final TieBreak tieBreak;
+
+   private DirectionalPool(String zoneId, String id, Optional<String> description, String dname, Type type,
+         TieBreak tieBreak) {
+      this.zoneId = checkNotNull(zoneId, "zoneId");
+      this.id = checkNotNull(id, "id");
+      this.description = checkNotNull(description, "description for %s", id);
+      this.dname = checkNotNull(dname, "dname for %s", id);
+      this.type = type;
+      this.tieBreak = tieBreak;
+   }
+
+   public String getZoneId() {
+      return zoneId;
+   }
+
+   public String getId() {
+      return id;
+   }
+
+   /**
+    * The dname of the pool. ex. {@code jclouds.org.}
+    */
+   public String getName() {
+      return dname;
+   }
+
+   /**
+    * The description of the pool. ex. {@code My Pool}
+    */
+   public Optional<String> getDescription() {
+      return description;
+   }
+
+   public Type getType() {
+      return type;
+   }
+
+   /**
+    * if {@link #getType} is {@link Type#MIXED}, this can be
+    * {@link TieBreak#SOURCEIP} or {@link TieBreak#GEOLOCATION}, otherwise
+    * {@link TieBreak#GEOLOCATION}
+    */
+   public TieBreak getTieBreak() {
+      return tieBreak;
+   }
+
+   public static enum Type {
+      GEOLOCATION, SOURCEIP, MIXED;
+   }
+
+   public static enum TieBreak {
+      GEOLOCATION, SOURCEIP;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(zoneId, id, description, dname);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null)
+         return false;
+      if (getClass() != obj.getClass())
+         return false;
+      DirectionalPool that = DirectionalPool.class.cast(obj);
+      return Objects.equal(this.zoneId, that.zoneId) && Objects.equal(this.id, that.id)
+            && Objects.equal(this.description, that.description) && Objects.equal(this.dname, that.dname);
+   }
+
+   @Override
+   public String toString() {
+      return Objects.toStringHelper(this).omitNullValues().add("zoneId", zoneId).add("id", id).add("name", dname)
+            .add("description", description.orNull()).add("type", type).add("tieBreak", tieBreak).toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().from(this);
+   }
+
+   public final static class Builder {
+      private String zoneId;
+      private String id;
+      private Optional<String> description = Optional.absent();
+      private String dname;
+      private Type type = Type.GEOLOCATION;
+      private TieBreak tieBreak = TieBreak.GEOLOCATION;
+
+      /**
+       * @see DirectionalPool#getZoneId()
+       */
+      public Builder zoneId(String zoneId) {
+         this.zoneId = zoneId;
+         return this;
+      }
+
+      /**
+       * @see DirectionalPool#getId()
+       */
+      public Builder id(String id) {
+         this.id = id;
+         return this;
+      }
+
+      /**
+       * @see DirectionalPool#getName()
+       */
+      public Builder name(String dname) {
+         this.dname = dname;
+         return this;
+      }
+
+      /**
+       * @see DirectionalPool#getDescription()
+       */
+      public Builder description(String description) {
+         this.description = Optional.fromNullable(description);
+         return this;
+      }
+
+      /**
+       * @see DirectionalPool#getType()
+       */
+      public Builder type(Type type) {
+         this.type = type;
+         return this;
+      }
+
+      /**
+       * @see DirectionalPool#getTieBreak()
+       */
+      public Builder tieBreak(TieBreak tieBreak) {
+         this.tieBreak = tieBreak;
+         return this;
+      }
+
+      public DirectionalPool build() {
+         return new DirectionalPool(zoneId, id, description, dname, type, tieBreak);
+      }
+
+      public Builder from(DirectionalPool in) {
+         return this.zoneId(in.zoneId).id(in.id).description(in.description.orNull()).name(in.dname).type(in.type)
+               .tieBreak(in.tieBreak);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecord.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecord.java
new file mode 100644
index 0000000..2dcdbba
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecord.java
@@ -0,0 +1,174 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Functions.toStringFunction;
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+
+import java.util.List;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * @author Adrian Cole
+ */
+public class DirectionalRecord {
+   private final String type;
+   private final int ttl;
+   private final boolean noResponseRecord;
+   private final List<String> infoValues;
+
+   private DirectionalRecord(String type, int ttl, boolean noResponseRecord, List<String> infoValues) {
+      this.type = checkNotNull(type, "type");
+      checkArgument(ttl >= 0, "ttl must be >= 0");
+      this.ttl = ttl;
+      this.noResponseRecord = noResponseRecord;
+      this.infoValues = checkNotNull(infoValues, "infoValues");
+   }
+
+   /**
+    * the type. ex. {@code A}
+    */
+   public String getType() {
+      return type;
+   }
+
+   public int getTTL() {
+      return ttl;
+   }
+
+   /**
+    * true if blocks traffic from specified regions by returning No Error, No
+    * Response.
+    */
+   public boolean isNoResponseRecord() {
+      return noResponseRecord;
+   }
+
+   /**
+    * {@link #getType() type}-specific binary values.
+    */
+   public List<String> getRData() {
+      return infoValues;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(noResponseRecord, type, ttl, infoValues);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
+      DirectionalRecord that = DirectionalRecord.class.cast(obj);
+      return equal(this.type, that.type) && equal(this.ttl, that.ttl)
+            && equal(this.noResponseRecord, that.noResponseRecord) && equal(this.infoValues, that.infoValues);
+   }
+
+   @Override
+   public String toString() {
+      return toStringHelper(this).add("type", type).add("ttl", ttl).add("noResponseRecord", noResponseRecord)
+            .add("infoValues", infoValues).toString();
+   }
+
+   public static Builder drBuilder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return drBuilder().from(this);
+   }
+
+   public final static class Builder {
+      private String type;
+      private int ttl = -1;
+      private boolean noResponseRecord;
+      private ImmutableList.Builder<String> infoValues = ImmutableList.<String> builder();
+
+      /**
+       * @see DirectionalRecord#getType()
+       */
+      public Builder type(String type) {
+         this.type = type;
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecord#getTTL()
+       */
+      public Builder ttl(int ttl) {
+         this.ttl = ttl;
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecord#isNoResponseRecord()
+       */
+      public Builder noResponseRecord(boolean noResponseRecord) {
+         this.noResponseRecord = noResponseRecord;
+         return this;
+      }
+
+      /**
+       * adds to current values
+       * 
+       * @see DirectionalRecord#getRData()
+       */
+      public Builder infoValue(Object infoValue) {
+         this.infoValues.add(infoValue.toString());
+         return this;
+      }
+
+      /**
+       * replaces current values
+       * 
+       * @see DirectionalRecord#getRData()
+       */
+      public Builder rdata(Object infoValue) {
+         this.infoValues = ImmutableList.<String> builder().add(infoValue.toString());
+         return this;
+      }
+
+      /**
+       * replaces current values
+       * 
+       * @see DirectionalRecord#getRData()
+       */
+      public Builder rdata(Iterable<?> infoValues) {
+         this.infoValues = ImmutableList.<String> builder().addAll(transform(infoValues, toStringFunction()));
+         return this;
+      }
+
+      public DirectionalRecord build() {
+         return new DirectionalRecord(type, ttl, noResponseRecord, infoValues.build());
+      }
+
+      public Builder from(DirectionalRecord in) {
+         return type(in.type).ttl(in.ttl).noResponseRecord(in.noResponseRecord).rdata(in.infoValues);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecordDetail.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecordDetail.java
new file mode 100644
index 0000000..ecd9456
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecordDetail.java
@@ -0,0 +1,203 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+
+/**
+ * @author Adrian Cole
+ */
+public class DirectionalRecordDetail {
+
+   private final String zoneName;
+   private final String name;
+   private final String id;
+   private final Optional<DirectionalGroup> group;
+   private final Optional<DirectionalGroup> geolocationGroup;
+   private final Optional<DirectionalGroup> sourceIpGroup;
+   private final DirectionalRecord record;
+
+   private DirectionalRecordDetail(String zoneName, String name, String id,
+         Optional<DirectionalGroup> geolocationGroup, Optional<DirectionalGroup> group,
+         Optional<DirectionalGroup> sourceIpGroup, DirectionalRecord record) {
+      this.zoneName = checkNotNull(zoneName, "zoneName");
+      this.name = checkNotNull(name, "name");
+      this.id = checkNotNull(id, "id");
+      this.group = checkNotNull(group, "group of %s/%s/%s", zoneName, name, id);
+      this.geolocationGroup = checkNotNull(geolocationGroup, "geolocationGroup of %s/%s/%s", zoneName, name, id);
+      this.sourceIpGroup = checkNotNull(sourceIpGroup, "sourceIpGroup of %s/%s/%s", zoneName, name, id);
+      this.record = checkNotNull(record, "record of %s/%s/%s", zoneName, name, id);
+   }
+
+   public String getZoneName() {
+      return zoneName;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public String getId() {
+      return id;
+   }
+
+   /**
+    * group containing all regions that you have not specifically configured in {@link #getGeolocationGroup()}
+    */
+   public Optional<DirectionalGroup> getGroup() {
+      return group;
+   }
+
+   /**
+    * group containing territories.
+    */
+   public Optional<DirectionalGroup> getGeolocationGroup() {
+      return geolocationGroup;
+   }
+
+   /**
+    * group containing IPV4 or IPV6 ranges. 
+    */
+   public Optional<DirectionalGroup> getSourceIpGroup() {
+      return sourceIpGroup;
+   }
+
+   public DirectionalRecord getRecord() {
+      return record;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(zoneName, name, id);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
+      DirectionalRecordDetail that = DirectionalRecordDetail.class.cast(obj);
+      return equal(this.zoneName, that.zoneName) && equal(this.name, that.name) && equal(this.id, that.id);
+   }
+
+   @Override
+   public String toString() {
+      return toStringHelper(this).omitNullValues().add("zoneName", zoneName).add("name", name).add("id", id)
+            .add("group", group.orNull()).add("geolocationGroup", geolocationGroup.orNull())
+            .add("sourceIpGroup", sourceIpGroup.orNull()).add("record", record).toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().from(this);
+   }
+
+   public final static class Builder {
+      private String zoneName;
+      private String name;
+      private String id;
+      private Optional<DirectionalGroup> group = Optional.absent();
+      private Optional<DirectionalGroup> geolocationGroup = Optional.absent();
+      private Optional<DirectionalGroup> sourceIpGroup = Optional.absent();
+      private DirectionalRecord record;
+
+      /**
+       * @see DirectionalRecordDetail#getZoneName()
+       */
+      public Builder zoneName(String zoneName) {
+         this.zoneName = zoneName;
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecordDetail#getName()
+       */
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecordDetail#getId()
+       */
+      public Builder id(String id) {
+         this.id = id;
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecordDetail#getGroup()
+       */
+      public Builder group(DirectionalGroup group) {
+         this.group = Optional.fromNullable(group);
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecordDetail#getGeolocationGroup()
+       */
+      public Builder geolocationGroup(DirectionalGroup geolocationGroup) {
+         this.geolocationGroup = Optional.fromNullable(geolocationGroup);
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecordDetail#getSourceIpGroup()
+       */
+      public Builder sourceIpGroup(DirectionalGroup sourceIpGroup) {
+         this.sourceIpGroup = Optional.fromNullable(sourceIpGroup);
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecordDetail#getRecord()
+       */
+      public Builder record(DirectionalRecord record) {
+         this.record = record;
+         return this;
+      }
+
+      /**
+       * @see DirectionalRecordDetail#getRecord()
+       */
+      public Builder record(DirectionalRecord.Builder record) {
+         this.record = record.build();
+         return this;
+      }
+
+      public DirectionalRecordDetail build() {
+         return new DirectionalRecordDetail(zoneName, name, id, group, geolocationGroup, sourceIpGroup, record);
+      }
+
+      public Builder from(DirectionalRecordDetail in) {
+         return this.zoneName(in.zoneName).name(in.name).id(in.id).group(in.group.orNull())
+               .geolocationGroup(in.geolocationGroup.orNull()).sourceIpGroup(in.sourceIpGroup.orNull())
+               .record(in.record);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecordType.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecordType.java
new file mode 100644
index 0000000..97b4b6b
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/DirectionalRecordType.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+/**
+ * currently supported {@link ResourceRecord#getType() types} for directional
+ * groups.
+ * 
+ * @author Adrian Cole
+ */
+public enum DirectionalRecordType {
+   // A/CNAME
+   IPV4(1),
+
+   // AAAA/CNAME
+   IPV6(28),
+
+   TXT(16),
+
+   SRV(33),
+
+   PTR(12),
+
+   RP(17),
+
+   HINFO(13),
+
+   NAPTR(35),
+
+   MX(15);
+
+   private final int code;
+
+   private DirectionalRecordType(int code) {
+      this.code = code;
+   }
+
+   /**
+    * The {@link ResourceRecord#getType() type} that can be used in directional
+    * groups.
+    */
+   public int getCode() {
+      return code;
+   }
+
+}
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/Region.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/Region.java
new file mode 100644
index 0000000..23ea0fd
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/Region.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Set;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ForwardingSet;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * A region is a set of territory names.
+ * 
+ * @author Adrian Cole
+ */
+public class Region extends ForwardingSet<String> {
+
+   private final String name;
+   private final Set<String> territoryNames;
+
+   private Region(String name, Set<String> territoryNames) {
+      this.name = checkNotNull(name, "name");
+      this.territoryNames = checkNotNull(territoryNames, "territoryNames of %s", name);
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   public Set<String> getTerritoryNames() {
+      return territoryNames;
+   }
+
+   @Override
+   protected Set<String> delegate() {
+      return territoryNames;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(name, territoryNames);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
+      Region that = Region.class.cast(obj);
+      return equal(this.name, that.name) && equal(this.territoryNames, that.territoryNames);
+   }
+
+   @Override
+   public String toString() {
+      return toStringHelper(this).add("name", name).add("territoryNames", territoryNames).toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().from(this);
+   }
+
+   public final static class Builder {
+      private String name;
+      private ImmutableSet.Builder<String> territoryNames = ImmutableSet.<String> builder();
+
+      /**
+       * @see Region#getName()
+       */
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      /**
+       * adds to current territoryNames
+       * 
+       * @see Region#getTerritoryNames()
+       */
+      public Builder addTerritoryName(String territoryName) {
+         this.territoryNames.add(territoryName);
+         return this;
+      }
+
+      /**
+       * replaces current territoryNames
+       * 
+       * @see Region#getTerritoryNames()
+       */
+      public Builder territoryNames(Iterable<String> territoryNames) {
+         this.territoryNames = ImmutableSet.<String> builder().addAll(territoryNames);
+         return this;
+      }
+
+      public Region build() {
+         return new Region(name, territoryNames.build());
+      }
+
+      public Builder from(Region in) {
+         return name(in.getName()).territoryNames(in.getTerritoryNames());
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecord.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecord.java
index 08dc673..65850cc 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecord.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecord.java
@@ -92,7 +92,7 @@
 
    @Override
    public String toString() {
-      return toStringHelper("").omitNullValues().add("dName", dName).add("type", type).add("ttl", ttl)
+      return toStringHelper(this).omitNullValues().add("dName", dName).add("type", type).add("ttl", ttl)
             .add("infoValues", infoValues).toString();
    }
 
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecordMetadata.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecordMetadata.java
index af9bf39..b98ac37 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecordMetadata.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/domain/ResourceRecordMetadata.java
@@ -93,7 +93,7 @@
 
    @Override
    public String toString() {
-      return toStringHelper("").omitNullValues().add("zoneId", zoneId).add("guid", guid).add("zoneName", zoneName)
+      return toStringHelper(this).omitNullValues().add("zoneId", zoneId).add("guid", guid).add("zoneName", zoneName)
             .add("created", created).add("modified", modified).add("record", record).toString();
    }
 
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java
new file mode 100644
index 0000000..70e1436
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java
@@ -0,0 +1,135 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.features;
+
+import javax.inject.Named;
+import javax.ws.rs.POST;
+
+import org.jclouds.Fallbacks.EmptyFluentIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.Payload;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.VirtualHost;
+import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.binders.DirectionalGroupCoordinatesToXML;
+import org.jclouds.ultradns.ws.domain.AccountLevelGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupCoordinates;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupNameAndRegions;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordDetail;
+import org.jclouds.ultradns.ws.filters.SOAPWrapWithPasswordAuth;
+import org.jclouds.ultradns.ws.xml.AccountLevelGroupsHandler;
+import org.jclouds.ultradns.ws.xml.DirectionalGroupNameAndRegionsHandler;
+import org.jclouds.ultradns.ws.xml.DirectionalRecordDetailListHandler;
+import org.jclouds.ultradns.ws.xml.ItemListHandler;
+
+import com.google.common.collect.FluentIterable;
+
+/**
+ * @see <a href="https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01?wsdl" />
+ * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
+ * @author Adrian Cole
+ */
+@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@VirtualHost
+public interface DirectionalGroupApi {
+
+   /**
+    * returns the regions and name of the specified directional group or null,
+    * if not found.
+    * 
+    * @param groupId
+    *           the {@link DirectionalGroup#getId() id} of the group
+    */
+   @Named("getDirectionalDNSGroupDetails")
+   @POST
+   @XMLResponseParser(DirectionalGroupNameAndRegionsHandler.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Payload("<v01:getDirectionalDNSGroupDetails><GroupId>{GroupId}</GroupId></v01:getDirectionalDNSGroupDetails>")
+   @Nullable
+   DirectionalGroupNameAndRegions get(@PayloadParam("GroupId") String groupId);
+
+   /**
+    * Returns all account-level groups.
+    * 
+    */
+   @Named("getAccountLevelDirectionalGroupsOfZone")
+   @POST
+   @XMLResponseParser(AccountLevelGroupsHandler.class)
+   @Payload("<v01:getAccountLevelDirectionalGroups><accountId>{accountId}</accountId><GroupType /></v01:getAccountLevelDirectionalGroups>")
+   FluentIterable<AccountLevelGroup> listAccountLevelGroups();
+
+   /**
+    * Returns all the directional pool records in the account-level group.
+    * 
+    * @param groupId
+    *           the id of the account-level group containing the records.
+    * @throws ResourceNotFoundException
+    *            if the group doesn't exist
+    */
+   @Named("getDirectionalDNSRecordsForAcctLvlGroup")
+   @POST
+   @XMLResponseParser(DirectionalRecordDetailListHandler.class)
+   @Payload("<v01:getDirectionalDNSRecordsForAcctLvlGroup><groupId>{groupId}</groupId></v01:getDirectionalDNSRecordsForAcctLvlGroup>")
+   FluentIterable<DirectionalRecordDetail> listRecordsByAccountLevelGroup(
+         @PayloadParam("groupId") String groupId) throws ResourceNotFoundException;
+
+   /**
+    * Returns directional group names visible to the account for the fully
+    * qualified {@link hostName} and {@link rrType}
+    * 
+    * @param accountId
+    *           the account where the groups exist
+    * @param hostName
+    *           fully qualified hostname including the trailing dot.
+    * @param rrType
+    *           type value, with special casing: for {@code A} or {@code CNAME}
+    *           of ipv4 hosts, this is {@code 1}; for {@code AAAA} or
+    *           {@code CNAME} of ipv4 hosts, this is {@code 28}
+    * @return empty if there are not groups for the specified host and type
+    */
+   @Named("getAvailableGroups")
+   @POST
+   @XMLResponseParser(ItemListHandler.class)
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   @Payload("<v01:getAvailableGroups><poolName>{hostName}</poolName><poolRecordType>{rrType}</poolRecordType><accountID>{accountId}</accountID><groupType /></v01:getAvailableGroups>")
+   FluentIterable<String> listGroupNamesByRecordNameAndType(
+         @PayloadParam("hostName") String hostName, @PayloadParam("rrType") int rrType);
+
+   /**
+    * Returns all the directional pool records in the pool-level group.
+    * 
+    * @param group
+    *           the zone, record name, record type, and group name
+    * @throws ResourceNotFoundException
+    *            if the group doesn't exist
+    */
+   @Named("getDirectionalDNSRecordsForGroup")
+   @POST
+   @XMLResponseParser(DirectionalRecordDetailListHandler.class)
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   FluentIterable<DirectionalRecordDetail> listRecordsByGroupCoordinates(
+         @BinderParam(DirectionalGroupCoordinatesToXML.class) DirectionalGroupCoordinates group)
+         throws ResourceNotFoundException;
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java
new file mode 100644
index 0000000..59ed329
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.features;
+
+import javax.inject.Named;
+import javax.ws.rs.POST;
+
+import org.jclouds.Fallbacks.EmptyFluentIterableOnNotFoundOr404;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.Payload;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.VirtualHost;
+import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.domain.DirectionalPool;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordDetail;
+import org.jclouds.ultradns.ws.filters.SOAPWrapWithPasswordAuth;
+import org.jclouds.ultradns.ws.xml.DirectionalPoolListHandler;
+import org.jclouds.ultradns.ws.xml.DirectionalRecordDetailListHandler;
+
+import com.google.common.collect.FluentIterable;
+
+/**
+ * @see <a href="https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01?wsdl" />
+ * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
+ * @author Adrian Cole
+ */
+@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@VirtualHost
+public interface DirectionalPoolApi {
+
+   /**
+    * Returns all directional pools in the zone.
+    * 
+    * @throws ResourceNotFoundException
+    *            if the zone doesn't exist
+    */
+   @Named("getDirectionalPoolsOfZone")
+   @POST
+   @XMLResponseParser(DirectionalPoolListHandler.class)
+   @Payload("<v01:getDirectionalPoolsOfZone><zoneName>{zoneName}</zoneName></v01:getDirectionalPoolsOfZone>")
+   FluentIterable<DirectionalPool> list() throws ResourceNotFoundException;
+
+   /**
+    * Returns all the directional pool records in the zone with the fully
+    * qualified {@link hostName} and {@link rrType}
+    * 
+    * @param hostName
+    *           fully qualified hostname including the trailing dot.
+    * @param rrType
+    *           type value, with special casing: for {@code A} or {@code CNAME}
+    *           of ipv4 hosts, this is {@code 1}; for {@code AAAA} or
+    *           {@code CNAME} of ipv4 hosts, this is {@code 28}
+    * @return empty if there are not pools for the specified host or no records
+    *         exist for the type.
+    * @throws ResourceNotFoundException
+    *            if the zone doesn't exist
+    */
+   @Named("getDirectionalDNSRecordsForHost")
+   @POST
+   @XMLResponseParser(DirectionalRecordDetailListHandler.class)
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   @Payload("<v01:getDirectionalDNSRecordsForHost><zoneName>{zoneName}</zoneName><hostName>{hostName}</hostName><poolRecordType>{poolRecordType}</poolRecordType></v01:getDirectionalDNSRecordsForHost>")
+   FluentIterable<DirectionalRecordDetail> listRecordsByNameAndType(
+         @PayloadParam("hostName") String dname, @PayloadParam("poolRecordType") int type)
+         throws ResourceNotFoundException;
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java
index 3a59808..e541901 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java
@@ -96,6 +96,10 @@
        */
       static final int RESOURCE_RECORD_ALREADY_EXISTS = 2111;
       /**
+       * No Pool or Multiple pools of same type exists for the PoolName
+       */
+      static final int DIRECTIONAL_POOL_NOT_FOUND = 2142;
+      /**
        * Account not found in the system.
        */
       static final int ACCOUNT_NOT_FOUND = 2401;
@@ -111,6 +115,10 @@
        * Pool Record does not exist.
        */
       static final int POOL_RECORD_NOT_FOUND = 3101;
+      /**
+       * Group does not exist.
+       */
+      static final int GROUP_NOT_FOUND = 4003;
    }
 
    private Exception refineException(UltraDNSWSResponseException exception) {
@@ -125,7 +133,9 @@
       case RESOURCE_RECORD_NOT_FOUND:
       case ACCOUNT_NOT_FOUND:
       case POOL_NOT_FOUND:
+      case DIRECTIONAL_POOL_NOT_FOUND:
       case POOL_RECORD_NOT_FOUND:
+      case GROUP_NOT_FOUND:
          return new ResourceNotFoundException(message, exception);
       case ZONE_ALREADY_EXISTS:
       case RESOURCE_RECORD_ALREADY_EXISTS:
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/AccountLevelGroupsHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/AccountLevelGroupsHandler.java
new file mode 100644
index 0000000..4a6985c
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/AccountLevelGroupsHandler.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.xml;
+
+import static org.jclouds.util.SaxUtils.cleanseAttributes;
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.Map;
+
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.ultradns.ws.domain.AccountLevelGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalPool.Type;
+import org.xml.sax.Attributes;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class AccountLevelGroupsHandler extends
+      ParseSax.HandlerForGeneratedRequestWithResult<FluentIterable<AccountLevelGroup>> {
+
+   private final Builder<AccountLevelGroup> groups = ImmutableSet.<AccountLevelGroup> builder();
+
+   @Override
+   public FluentIterable<AccountLevelGroup> getResult() {
+      return FluentIterable.from(groups.build());
+   }
+
+   @Override
+   public void startElement(String url, String name, String qName, Attributes attrs) {
+      if (equalsOrSuffix(qName, "AccountLevelGroups")) {
+         Map<String, String> attributes = cleanseAttributes(attrs);
+         groups.add(AccountLevelGroup.builder()
+                                     .id(attributes.get("GroupId"))
+                                     .name(attributes.get("GroupName"))
+                                     .type(Type.valueOf(attributes.get("GroupType")))
+                                     .recordCount(Integer.parseInt(attributes.get("RecordsCount")))
+                                     .build());
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalGroupNameAndRegionsHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalGroupNameAndRegionsHandler.java
new file mode 100644
index 0000000..800a9bd
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalGroupNameAndRegionsHandler.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupNameAndRegions;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupNameAndRegions.Builder;
+import org.jclouds.ultradns.ws.domain.Region;
+import org.xml.sax.Attributes;
+
+import com.google.common.base.Splitter;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class DirectionalGroupNameAndRegionsHandler extends ParseSax.HandlerForGeneratedRequestWithResult<DirectionalGroupNameAndRegions> {
+
+   private final Builder group = DirectionalGroupNameAndRegions.builder();
+
+   @Override
+   public DirectionalGroupNameAndRegions getResult() {
+      return group.build();
+   }
+
+   @Override
+   public void startElement(String url, String name, String qName, Attributes attrs) {
+      if (equalsOrSuffix(qName, "DirectionalDNSGroupDetail")) {
+         group.name(attrs.getValue("GroupName"));
+      } else if (equalsOrSuffix(qName, "RegionForNewGroups")) {
+         Iterable<String> territories = Splitter.on(';').split(attrs.getValue("TerritoryName"));
+         Region region = Region.builder()
+                               .name(attrs.getValue("RegionName"))
+                               .territoryNames(territories).build();
+         group.addRegion(region);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalPoolListHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalPoolListHandler.java
new file mode 100644
index 0000000..70c2718
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalPoolListHandler.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.xml;
+
+import static org.jclouds.util.SaxUtils.cleanseAttributes;
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.Map;
+
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.ultradns.ws.domain.DirectionalPool;
+import org.jclouds.ultradns.ws.domain.DirectionalPool.TieBreak;
+import org.jclouds.ultradns.ws.domain.DirectionalPool.Type;
+import org.xml.sax.Attributes;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class DirectionalPoolListHandler extends ParseSax.HandlerForGeneratedRequestWithResult<FluentIterable<DirectionalPool>> {
+
+   private final Builder<DirectionalPool> pools = ImmutableSet.<DirectionalPool> builder();
+
+   @Override
+   public FluentIterable<DirectionalPool> getResult() {
+      return FluentIterable.from(pools.build());
+   }
+
+   @Override
+   public void startElement(String url, String name, String qName, Attributes attrs) {
+      if (equalsOrSuffix(qName, "DirectionalPoolData")) {
+         Map<String, String> attributes = cleanseAttributes(attrs);
+
+         DirectionalPool.Builder pool = DirectionalPool.builder()
+                                                       .zoneId(attributes.get("Zoneid"))
+                                                       .id(attributes.get("dirpoolid"))
+                                                       .name(attributes.get("Pooldname"))
+                                                       .description(attributes.get("Description"));
+
+         String type = attributes.get("DirPoolType");
+         if (type != null)
+            pool.type(Type.valueOf(type));
+         
+         String tieBreak = attributes.get("TieBreak");
+         if (tieBreak != null)
+            pool.tieBreak(TieBreak.valueOf(tieBreak));
+
+         pools.add(pool.build());
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalRecordDetailHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalRecordDetailHandler.java
new file mode 100644
index 0000000..7c967aa
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalRecordDetailHandler.java
@@ -0,0 +1,90 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.xml;
+
+import static org.jclouds.util.SaxUtils.cleanseAttributes;
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.Map;
+
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.ultradns.ws.domain.DirectionalGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalRecord;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordDetail;
+import org.xml.sax.Attributes;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class DirectionalRecordDetailHandler extends
+      ParseSax.HandlerForGeneratedRequestWithResult<DirectionalRecordDetail> {
+
+   private DirectionalRecordDetail.Builder drd = DirectionalRecordDetail.builder();
+   private DirectionalRecord.Builder dr = DirectionalRecord.drBuilder();
+
+   private String zoneName;
+   private String dname;
+
+   @Override
+   public DirectionalRecordDetail getResult() {
+      try {
+         return drd.record(dr.build()).build();
+      } finally {
+         drd = DirectionalRecordDetail.builder().zoneName(zoneName).name(dname);
+         dr = DirectionalRecord.drBuilder();
+      }
+   }
+
+   @Override
+   public void startElement(String uri, String localName, String qName, Attributes attrs) {
+      Map<String, String> attributes = cleanseAttributes(attrs);
+      if (attributes.containsKey("ZoneName")) {
+         zoneName = attributes.get("ZoneName");
+         dname = attributes.get("DName");
+         drd.zoneName(zoneName).name(dname);
+      }
+      if (attributes.containsKey("DirPoolRecordId")) {
+         drd.id(attributes.get("DirPoolRecordId"));
+      }
+      if (attributes.containsKey("GroupId")) {
+         drd.group(DirectionalGroup.builder()
+                                   .id(attributes.get("GroupId"))
+                                   .name(attributes.get("GroupName")).build());
+      }
+      if (attributes.containsKey("GeolocationGroupId")) {
+         drd.geolocationGroup(DirectionalGroup.builder()
+                                              .id(attributes.get("GeolocationGroupId"))
+                                              .name(attributes.get("GeolocationGroupName")).build());
+      }
+      if (attributes.containsKey("SourceIPGroupId")) {
+         drd.sourceIpGroup(DirectionalGroup.builder()
+                                           .id(attributes.get("SourceIPGroupId"))
+                                           .name(attributes.get("SourceIPGroupName")).build());
+      }
+      if (attributes.containsKey("recordType")) {
+         dr.type(attributes.get("recordType"));
+         dr.ttl(Integer.parseInt(attributes.get("TTL")));
+         dr.noResponseRecord("true".equalsIgnoreCase(attributes.get("noResponseRecord")));
+      }
+      if (equalsOrSuffix(qName, "InfoValues")) {
+         dr.rdata(attributes.values());
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalRecordDetailListHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalRecordDetailListHandler.java
new file mode 100644
index 0000000..76cc2e8
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/DirectionalRecordDetailListHandler.java
@@ -0,0 +1,64 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.xml;
+
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordDetail;
+import org.xml.sax.Attributes;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.google.inject.Inject;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class DirectionalRecordDetailListHandler extends
+      ParseSax.HandlerForGeneratedRequestWithResult<FluentIterable<DirectionalRecordDetail>> {
+
+   private final DirectionalRecordDetailHandler directionalRecordHandler;
+
+   private final Builder<DirectionalRecordDetail> drs = ImmutableSet.<DirectionalRecordDetail> builder();
+
+   @Inject
+   public DirectionalRecordDetailListHandler(DirectionalRecordDetailHandler directionalRecordHandler) {
+      this.directionalRecordHandler = directionalRecordHandler;
+   }
+
+   @Override
+   public FluentIterable<DirectionalRecordDetail> getResult() {
+      return FluentIterable.from(drs.build());
+   }
+
+   @Override
+   public void startElement(String url, String name, String qName, Attributes attributes) {
+      directionalRecordHandler.startElement(url, name, qName, attributes);
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      if (equalsOrSuffix(qName, "DirectionalDNSRecordDetail")) {
+         drs.add(directionalRecordHandler.getResult());
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ItemListHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ItemListHandler.java
new file mode 100644
index 0000000..4b0b99d
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ItemListHandler.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.xml;
+
+import static org.jclouds.util.SaxUtils.currentOrNull;
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import org.jclouds.http.functions.ParseSax;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class ItemListHandler extends ParseSax.HandlerForGeneratedRequestWithResult<FluentIterable<String>> {
+
+   private StringBuilder currentText = new StringBuilder();
+
+   private Builder<String> items = ImmutableSet.<String> builder();
+
+   @Override
+   public FluentIterable<String> getResult() {
+      return FluentIterable.from(items.build());
+   }
+
+   @Override
+   public void endElement(String uri, String name, String qName) {
+      if (equalsOrSuffix(qName, "item")) {
+         items.add(currentOrNull(currentText));
+      }
+      currentText = new StringBuilder();
+   }
+
+   @Override
+   public void characters(char ch[], int start, int length) {
+      currentText.append(ch, start, length);
+   }
+}
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/RegionListHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/RegionListHandler.java
new file mode 100644
index 0000000..6c47f3b
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/RegionListHandler.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.xml;
+
+import static org.jclouds.util.SaxUtils.cleanseAttributes;
+import static org.jclouds.util.SaxUtils.equalsOrSuffix;
+
+import java.util.Map;
+
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.ultradns.ws.domain.Region;
+import org.xml.sax.Attributes;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class RegionListHandler extends ParseSax.HandlerForGeneratedRequestWithResult<Map<Integer, Region>> {
+
+   private final Builder<Integer, Region> regions = ImmutableMap.<Integer, Region> builder();
+
+   @Override
+   public Map<Integer, Region> getResult() {
+      return regions.build();
+   }
+
+   @Override
+   public void startElement(String url, String name, String qName, Attributes attrs) {
+      if (equalsOrSuffix(qName, "Region")) {
+         Map<String, String> attributes = cleanseAttributes(attrs);
+         int id = Integer.parseInt(attributes.get("RegionID"));
+         Iterable<String> territories = Splitter.on(';').split(attributes.get("TerritoryName"));
+         Region region = Region.builder()
+                               .name(attributes.get("RegionName"))
+                               .territoryNames(territories).build();
+         regions.put(id, region);
+      }
+   }
+}
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiExpectTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiExpectTest.java
index 7398d45..5ad9827 100644
--- a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiExpectTest.java
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiExpectTest.java
@@ -26,6 +26,7 @@
 import org.jclouds.http.HttpResponse;
 import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiExpectTest;
 import org.jclouds.ultradns.ws.parse.GetAccountsListOfUserResponseTest;
+import org.jclouds.ultradns.ws.parse.GetAvailableRegionsResponseTest;
 import org.testng.annotations.Test;
 
 /**
@@ -50,4 +51,21 @@
             success.getCurrentAccount().toString(),
             new GetAccountsListOfUserResponseTest().expected().toString());
    }
+
+   HttpRequest getRegionsById = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/list_regions.xml", "application/xml")).build();
+
+   HttpResponse getRegionsByIdResponse = HttpResponse.builder().statusCode(200)
+         .payload(payloadFromResourceWithContentType("/regions.xml", "application/xml")).build();
+
+   public void testGetRegionsByIdWhenResponseIs2xx() {
+
+      UltraDNSWSApi success = requestSendsResponse(getRegionsById, getRegionsByIdResponse);
+
+      assertEquals(
+            success.getRegionsById().toString(),
+            new GetAvailableRegionsResponseTest().expected().toString());
+   }
 }
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiLiveTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiLiveTest.java
index bfc2f49..d6046fd 100644
--- a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiLiveTest.java
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/UltraDNSWSApiLiveTest.java
@@ -18,9 +18,14 @@
  */
 package org.jclouds.ultradns.ws;
 
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map.Entry;
 
 import org.jclouds.ultradns.ws.domain.Account;
+import org.jclouds.ultradns.ws.domain.Region;
 import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiLiveTest;
 import org.testng.annotations.Test;
 
@@ -40,4 +45,18 @@
       assertNotNull(account.getId(), "Id cannot be null for " + account);
       assertNotNull(account.getName(), "Name cannot be null for " + account);
    }
+
+   @Test
+   public void testListRegions() {
+      for (Entry<Integer, Region> region : api.getRegionsById().entrySet()) {
+         checkRegion(region);
+      }
+   }
+
+   private void checkRegion(Entry<Integer, Region> region) {
+      assertTrue(region.getKey() > 0, "Id cannot be negative " + region);
+      assertNotNull(region.getValue().getName(), "Name cannot be null " + region);
+      assertNotNull(region.getValue().getTerritoryNames(), "TerritoryNames cannot be null " + region);
+      assertFalse(region.getValue().getTerritoryNames().isEmpty(), "TerritoryNames cannot be empty " + region);
+   }
 }
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalGroupApiExpectTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalGroupApiExpectTest.java
new file mode 100644
index 0000000..c65bb42
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalGroupApiExpectTest.java
@@ -0,0 +1,128 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.features;
+import static com.google.common.net.HttpHeaders.HOST;
+import static javax.ws.rs.HttpMethod.POST;
+import static javax.ws.rs.core.Response.Status.OK;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.ultradns.ws.UltraDNSWSApi;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupCoordinates;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiExpectTest;
+import org.jclouds.ultradns.ws.parse.GetAccountLevelDirectionalGroupsResponseTest;
+import org.jclouds.ultradns.ws.parse.GetAvailableGroupsResponseTest;
+import org.jclouds.ultradns.ws.parse.GetDirectionalDNSGroupDetailsResponseTest;
+import org.jclouds.ultradns.ws.parse.GetDirectionalDNSRecordsForHostResponseTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "DirectionalGroupApiExpectTest")
+public class DirectionalGroupApiExpectTest extends BaseUltraDNSWSApiExpectTest {
+
+   HttpRequest listGroupNamesByRecordNameAndType = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/list_directionalgroup_names.xml", "application/xml")).build();
+
+   HttpResponse listGroupNamesByRecordNameAndTypeResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+
+         .payload(payloadFromResourceWithContentType("/directionalgroup_names.xml", "application/xml")).build();
+   
+   public void testListGroupNamesByRecordNameAndTypeWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(listGroupNamesByRecordNameAndType, listGroupNamesByRecordNameAndTypeResponse);
+
+      assertEquals(success.getDirectionalGroupApiForAccount("accountid").listGroupNamesByRecordNameAndType("www.jclouds.org.", 1).toString(),
+            new GetAvailableGroupsResponseTest().expected().toString());
+   }
+
+   HttpRequest listRecordsByGroupCoordinates = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/list_directionalgroup_records.xml", "application/xml")).build();
+
+   HttpResponse listRecordsByGroupCoordinatesResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+
+         .payload(payloadFromResourceWithContentType("/directionalrecords.xml", "application/xml")).build();
+
+   public void testListRecordsByGroupCoordinatesWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(listRecordsByGroupCoordinates, listRecordsByGroupCoordinatesResponse);
+
+      DirectionalGroupCoordinates group = DirectionalGroupCoordinates.builder()
+                                                                     .zoneName("jclouds.org.")
+                                                                     .recordName("www.jclouds.org.")
+                                                                     .recordType(1)
+                                                                     .groupName("EU-www.jclouds.org.").build();
+      assertEquals(
+            success.getDirectionalGroupApiForAccount("accountid").listRecordsByGroupCoordinates(group).toString(),
+            new GetDirectionalDNSRecordsForHostResponseTest().expected().toString());
+   }
+
+   HttpRequest get = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/get_directionalgroup.xml", "application/xml")).build();
+
+   HttpResponse getResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+
+         .payload(payloadFromResourceWithContentType("/directionalgroup.xml", "application/xml")).build();
+   
+   public void testGetWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(get, getResponse);
+
+      assertEquals(success.getDirectionalGroupApiForAccount("accountid").get("0000000000A").toString(),
+            new GetDirectionalDNSGroupDetailsResponseTest().expected().toString());
+   }
+
+   HttpRequest listAccountLevelGroups = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/list_accountlevelgroups.xml", "application/xml")).build();
+
+   HttpResponse listAccountLevelGroupsResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+
+         .payload(payloadFromResourceWithContentType("/accountlevelgroups.xml", "application/xml")).build();
+   
+   public void testListAccountLevelGroupsWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(listAccountLevelGroups, listAccountLevelGroupsResponse);
+
+      assertEquals(success.getDirectionalGroupApiForAccount("accountid").listAccountLevelGroups().toString(),
+            new GetAccountLevelDirectionalGroupsResponseTest().expected().toString());
+   }
+
+   HttpRequest listRecordsByAccountLevelGroup = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/list_accountlevelgroup_records.xml", "application/xml")).build();
+
+   HttpResponse listRecordsByAccountLevelGroupResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+
+         .payload(payloadFromResourceWithContentType("/directionalrecords.xml", "application/xml")).build();
+
+   public void testListRecordsByAccountLevelGroupWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(listRecordsByAccountLevelGroup, listRecordsByAccountLevelGroupResponse);
+
+      assertEquals(
+            success.getDirectionalGroupApiForAccount("accountid").listRecordsByAccountLevelGroup("000000000000000A").toString(),
+            new GetDirectionalDNSRecordsForHostResponseTest().expected().toString());
+   }
+}
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalGroupApiLiveTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalGroupApiLiveTest.java
new file mode 100644
index 0000000..0f67e65
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalGroupApiLiveTest.java
@@ -0,0 +1,126 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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 WATCANTIES 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.jclouds.ultradns.ws.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.ultradns.ws.domain.Account;
+import org.jclouds.ultradns.ws.domain.AccountLevelGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupCoordinates;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupNameAndRegions;
+import org.jclouds.ultradns.ws.domain.DirectionalPool;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordDetail;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordType;
+import org.jclouds.ultradns.ws.domain.Zone;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiLiveTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Sets;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "live", singleThreaded = true, testName = "DirectionalGroupApiLiveTest")
+public class DirectionalGroupApiLiveTest extends BaseUltraDNSWSApiLiveTest {
+
+   private Account account;
+
+   @Override
+   @BeforeClass(groups = { "integration", "live" })
+   public void setup() {
+      super.setup();
+      account = api.getCurrentAccount();
+   }
+
+   @Test
+   public void testListAccountLevelGroups() {
+      for (AccountLevelGroup group : api().listAccountLevelGroups()) {
+         checkGroup(group);
+      }
+   }
+
+   private void checkGroup(AccountLevelGroup group) {
+      assertNotNull(group.getId(), "Id cannot be null " + group);
+      assertNotNull(group.getName(), "Name cannot be null " + group);
+      assertNotNull(group.getType(), "Type cannot be null " + group);
+      assertTrue(group.getRecordCount() >= 0, "RecordCount must be positive " + group);
+   }
+
+   @Test
+   public void testListRecordsByAccountLevelGroup() {
+      for (AccountLevelGroup group : api().listAccountLevelGroups()) {
+         for (DirectionalRecordDetail rr : api().listRecordsByAccountLevelGroup(group.getId())) {
+            DirectionalPoolApiLiveTest.checkDirectionalRecordDetail(rr);
+         }
+      }
+   }
+
+   @Test
+   public void testGetDirectionalGroup() {
+      for (AccountLevelGroup group : api().listAccountLevelGroups()) {
+         DirectionalGroupNameAndRegions withRegions = api().get(group.getId());
+         assertEquals(withRegions.getName(), group.getName());
+         assertTrue(withRegions.size() > 0);
+      }
+   }
+
+   Set<DirectionalGroupCoordinates> allGroups = Sets.newLinkedHashSet();
+
+   @Test
+   public void testListGroupNamesByRecordNameAndType() {
+      for (Zone zone : api.getZoneApi().listByAccount(account.getId())) {
+         for (DirectionalPool pool : api.getDirectionalPoolApiForZone(zone.getName()).list()) {
+            for (DirectionalRecordType type : EnumSet.allOf(DirectionalRecordType.class)) {
+               for (String groupName : api().listGroupNamesByRecordNameAndType(pool.getName(), type.getCode())) {
+                  allGroups.add(DirectionalGroupCoordinates.builder()
+                                                           .zoneName(zone.getName())
+                                                           .recordName(pool.getName())
+                                                           .recordType(type.getCode())
+                                                           .groupName(groupName).build());
+               }
+            }
+         }
+      }
+   }
+
+   @Test(dependsOnMethods = "testListGroupNamesByRecordNameAndType")
+   public void testListRecordsByGroupCoordinates() {
+      for (DirectionalGroupCoordinates group : allGroups) {
+         for (DirectionalRecordDetail rr : api().listRecordsByGroupCoordinates(group)) {
+            DirectionalPoolApiLiveTest.checkDirectionalRecordDetail(rr);
+         }
+      }
+   }
+
+   @Test(expectedExceptions = ResourceNotFoundException.class, expectedExceptionsMessageRegExp = "Group does not exist.")
+   public void testListRecordsByAccountLevelGroupWhenGroupIdNotFound() {
+      api().listRecordsByAccountLevelGroup("AAAAAAAAAAAAAAAA");
+   }
+
+   private DirectionalGroupApi api() {
+      return api.getDirectionalGroupApiForAccount(account.getId());
+   }
+}
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalPoolApiExpectTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalPoolApiExpectTest.java
new file mode 100644
index 0000000..72a9814
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalPoolApiExpectTest.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.features;
+import static com.google.common.net.HttpHeaders.HOST;
+import static javax.ws.rs.HttpMethod.POST;
+import static javax.ws.rs.core.Response.Status.OK;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.ultradns.ws.UltraDNSWSApi;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiExpectTest;
+import org.jclouds.ultradns.ws.parse.GetDirectionalDNSRecordsForHostResponseTest;
+import org.jclouds.ultradns.ws.parse.GetDirectionalPoolsByZoneResponseTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "DirectionalPoolApiExpectTest")
+public class DirectionalPoolApiExpectTest extends BaseUltraDNSWSApiExpectTest {
+
+   HttpRequest list = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/list_directionalpools.xml", "application/xml")).build();
+
+   HttpResponse listResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+
+         .payload(payloadFromResourceWithContentType("/directionalpools.xml", "application/xml")).build();
+   
+   public void testListWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(list, listResponse);
+
+      assertEquals(success.getDirectionalPoolApiForZone("jclouds.org.").list().toString(),
+            new GetDirectionalPoolsByZoneResponseTest().expected().toString());
+   }
+
+   HttpRequest listRecords = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/list_directionalrecords.xml", "application/xml")).build();
+
+   HttpResponse listRecordsResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+
+         .payload(payloadFromResourceWithContentType("/directionalrecords.xml", "application/xml")).build();
+
+   public void testListRecordsWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(listRecords, listRecordsResponse);
+
+      assertEquals(
+            success.getDirectionalPoolApiForZone("jclouds.org.").listRecordsByNameAndType("www.jclouds.org.", 1).toString(),
+            new GetDirectionalDNSRecordsForHostResponseTest().expected().toString());
+   }
+}
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalPoolApiLiveTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalPoolApiLiveTest.java
new file mode 100644
index 0000000..b7929c0
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/DirectionalPoolApiLiveTest.java
@@ -0,0 +1,142 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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 WATCANTIES 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.jclouds.ultradns.ws.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.ultradns.ws.domain.Account;
+import org.jclouds.ultradns.ws.domain.DirectionalGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalPool;
+import org.jclouds.ultradns.ws.domain.DirectionalRecord;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordDetail;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordType;
+import org.jclouds.ultradns.ws.domain.Zone;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiLiveTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "live", singleThreaded = true, testName = "DirectionalPoolApiLiveTest")
+public class DirectionalPoolApiLiveTest extends BaseUltraDNSWSApiLiveTest {
+
+   private Account account;
+
+   @Override
+   @BeforeClass(groups = { "integration", "live" })
+   public void setup() {
+      super.setup();
+      account = api.getCurrentAccount();
+   }
+
+   @Test
+   public void testListDirectionalPools() {
+      for (Zone zone : api.getZoneApi().listByAccount(account.getId())) {
+         for (DirectionalPool pool : api(zone.getName()).list()) {
+            checkDirectional(pool);
+         }
+      }
+   }
+
+   private void checkDirectional(DirectionalPool pool) {
+      assertNotNull(pool.getZoneId(), "ZoneId cannot be null " + pool);
+      assertNotNull(pool.getId(), "Id cannot be null " + pool);
+      assertNotNull(pool.getName(), "DName cannot be null " + pool);
+      assertNotNull(pool.getDescription(), "Description cannot be null " + pool);
+      assertNotNull(pool.getType(), "Type cannot be null " + pool);
+      assertNotNull(pool.getTieBreak(), "TieBreak cannot be null " + pool);
+   }
+
+   Set<DirectionalGroup> allDirectionalGroups = Sets.newLinkedHashSet();
+
+   @Test
+   public void testListDirectionalRecords() {
+      for (Zone zone : api.getZoneApi().listByAccount(account.getId())) {
+         for (DirectionalPool pool : api(zone.getName()).list()) {
+            for (DirectionalRecordType type : EnumSet.allOf(DirectionalRecordType.class)) {
+               for (DirectionalRecordDetail rr : api(zone.getName())
+                     .listRecordsByNameAndType(pool.getName(), type.getCode())) {
+                  checkDirectionalRecordDetail(rr);
+                  Iterable<DirectionalGroup> groups = Optional.presentInstances(ImmutableSet.of(rr.getGroup(),
+                        rr.getGeolocationGroup(), rr.getGeolocationGroup()));
+                  assertFalse(Iterables.isEmpty(groups), "No groups " + rr);
+                  for (DirectionalGroup group : groups) {
+                     allDirectionalGroups.add(group);
+                     assertNotNull(group.getId(), "Id cannot be null " + group);
+                     assertNotNull(group.getName(), "Name cannot be null " + group);
+                  }
+                  assertEquals(rr.getZoneName(), zone.getName());
+                  assertEquals(rr.getName(), pool.getName());
+                  switch (pool.getType()) {
+                  case GEOLOCATION:
+                     assertNotNull(rr.getGeolocationGroup().or(rr.getGroup()).orNull(),
+                           "GeolocationGroup or Group must be present " + rr);
+                     assertNull(rr.getSourceIpGroup().orNull(), "SourceIpGroup must be absent " + rr);
+                     break;
+                  case SOURCEIP:
+                     assertNotNull(rr.getSourceIpGroup().orNull(), "SourceIpGroup must be present " + rr);
+                     assertNull(rr.getGeolocationGroup().orNull(), "GeolocationGroup must be absent " + rr);
+                     break;
+                  case MIXED:
+                     assertNotNull(rr.getGeolocationGroup().or(rr.getSourceIpGroup()).or(rr.getGroup()).orNull(),
+                           "GeolocationGroup, SourceIpGroup or Group must be present " + rr);
+                     break;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+   static void checkDirectionalRecord(DirectionalRecord rr) {
+      assertNotNull(rr.getType(), "Type cannot be null " + rr);
+      assertNotNull(rr.getTTL(), "TTL cannot be null " + rr);
+      assertNotNull(rr.getRData(), "InfoValues cannot be null " + rr);
+   }
+
+   static void checkDirectionalRecordDetail(DirectionalRecordDetail rr) {
+      assertNotNull(rr.getZoneName(), "ZoneName cannot be null " + rr);
+      assertNotNull(rr.getName(), "DName cannot be null " + rr);
+      assertNotNull(rr.getId(), "Id cannot be null " + rr);
+      assertNotNull(rr.getZoneName(), "ZoneName cannot be null " + rr);
+      checkDirectionalRecord(rr.getRecord());
+   }
+
+   @Test(expectedExceptions = ResourceNotFoundException.class, expectedExceptionsMessageRegExp = "Parent Zone does not exist in the system.")
+   public void testListDirectionalsWhenZoneIdNotFound() {
+      api("AAAAAAAAAAAAAAAA").list();
+   }
+
+   private DirectionalPoolApi api(String zoneName) {
+      return api.getDirectionalPoolApiForZone(zoneName);
+   }
+}
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java
index 876588c..bc71fee 100644
--- a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java
@@ -241,6 +241,30 @@
    }
 
    @Test
+   public void testCode2142SetsResourceNotFoundException() throws IOException {
+      HttpRequest request = HttpRequest.builder().method(POST)
+                                                 .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+                                                 .addHeader(HOST, "ultra-api.ultradns.com:8443")
+                                                 .payload(payloadFromResource("/delete_lbpool.xml")).build();
+      HttpCommand command = new HttpCommand(request);
+      HttpResponse response = HttpResponse.builder()
+                                          .message(INTERNAL_SERVER_ERROR.getReasonPhrase())
+                                          .statusCode(INTERNAL_SERVER_ERROR.getStatusCode())
+                                          .payload(payloadFromResource("/directionalpool_doesnt_exist.xml")).build();
+
+      function.handleError(command, response);
+
+      assertEquals(command.getException().getClass(), ResourceNotFoundException.class);
+      assertEquals(command.getException().getMessage(), "No Pool or Multiple pools of same type exists for the PoolName : foo.jclouds.org.");
+
+      UltraDNSWSResponseException exception = UltraDNSWSResponseException.class.cast(command.getException().getCause());
+
+      assertEquals(exception.getMessage(), "Error 2142: No Pool or Multiple pools of same type exists for the PoolName : foo.jclouds.org.");
+      assertEquals(exception.getError().getDescription().get(), "No Pool or Multiple pools of same type exists for the PoolName : foo.jclouds.org.");
+      assertEquals(exception.getError().getCode(), 2142);
+   }
+
+   @Test
    public void testCode2912SetsResourceAlreadyExistsException() throws IOException {
       HttpRequest request = HttpRequest.builder().method(POST)
                                                  .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
@@ -291,6 +315,30 @@
       assertEquals(exception.getError().getCode(), 3101);
    }
 
+   @Test
+   public void testCode4003SetsResourceNotFoundException() throws IOException {
+      HttpRequest request = HttpRequest.builder().method(POST)
+                                                 .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+                                                 .addHeader(HOST, "ultra-api.ultradns.com:8443")
+                                                 .payload(payloadFromResource("/delete_tcrecord.xml")).build();
+      HttpCommand command = new HttpCommand(request);
+      HttpResponse response = HttpResponse.builder()
+                                          .message(INTERNAL_SERVER_ERROR.getReasonPhrase())
+                                          .statusCode(INTERNAL_SERVER_ERROR.getStatusCode())
+                                          .payload(payloadFromResource("/directionalgroup_doesnt_exist.xml")).build();
+
+      function.handleError(command, response);
+
+      assertEquals(command.getException().getClass(), ResourceNotFoundException.class);
+      assertEquals(command.getException().getMessage(), "Group does not exist.");
+
+      UltraDNSWSResponseException exception = UltraDNSWSResponseException.class.cast(command.getException().getCause());
+
+      assertEquals(exception.getMessage(), "Error 4003: Group does not exist.");
+      assertEquals(exception.getError().getDescription().get(), "Group does not exist.");
+      assertEquals(exception.getError().getCode(), 4003);
+   }
+
    private Payload payloadFromResource(String resource) {
       try {
          return payloadFromStringWithContentType(toStringAndClose(getClass().getResourceAsStream(resource)),
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAccountLevelDirectionalGroupsResponseTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAccountLevelDirectionalGroupsResponseTest.java
new file mode 100644
index 0000000..4a3fede
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAccountLevelDirectionalGroupsResponseTest.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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 WADirectionalANTIES 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.jclouds.ultradns.ws.parse;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.jclouds.ultradns.ws.domain.AccountLevelGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalPool.Type;
+import org.jclouds.ultradns.ws.xml.AccountLevelGroupsHandler;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(testName = "GetAccountLevelDirectionalGroupsResponseTest")
+public class GetAccountLevelDirectionalGroupsResponseTest extends BaseHandlerTest {
+
+   public void test() {
+      InputStream is = getClass().getResourceAsStream("/accountlevelgroups.xml");
+
+      FluentIterable<AccountLevelGroup> expected = expected();
+
+      AccountLevelGroupsHandler handler = injector.getInstance(AccountLevelGroupsHandler.class);
+      FluentIterable<AccountLevelGroup> result = factory.create(handler).parse(is);
+
+      assertEquals(result.toSet().toString(), expected.toSet().toString());
+   }
+
+   public FluentIterable<AccountLevelGroup> expected() {
+      return FluentIterable.from(ImmutableList.<AccountLevelGroup> builder()
+                           .add(AccountLevelGroup.builder()
+                                                 .id("000000000000000A")
+                                                 .name("ASIA")
+                                                 .type(Type.GEOLOCATION)
+                                                 .recordCount(0).build())
+                           .add(AccountLevelGroup.builder()
+                                                 .id("000000000000000B")
+                                                 .name("EU")
+                                                 .type(Type.GEOLOCATION)
+                                                 .recordCount(3).build())
+                           .add(AccountLevelGroup.builder()
+                                                 .id("000000000000000C")
+                                                 .name("LATAM")
+                                                 .type(Type.GEOLOCATION)
+                                                 .recordCount(1).build()).build());
+   }
+}
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAvailableGroupsResponseTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAvailableGroupsResponseTest.java
new file mode 100644
index 0000000..79df598
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAvailableGroupsResponseTest.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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 WADirectionalANTIES 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.jclouds.ultradns.ws.parse;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.jclouds.ultradns.ws.xml.ItemListHandler;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(testName = "GetAvailableGroupsResponseTest")
+public class GetAvailableGroupsResponseTest extends BaseHandlerTest {
+
+   public void test() {
+      InputStream is = getClass().getResourceAsStream("/directionalgroup_names.xml");
+
+      FluentIterable<String> expected = expected();
+
+      ItemListHandler handler = injector.getInstance(ItemListHandler.class);
+      FluentIterable<String> result = factory.create(handler).parse(is);
+
+      assertEquals(result.toSet().toString(), expected.toSet().toString());
+   }
+
+   public FluentIterable<String> expected() {
+      return FluentIterable.from(ImmutableSet.of("EU-foo.jclouds.org.", "non-foo.jclouds.org."));
+   }
+}
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAvailableRegionsResponseTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAvailableRegionsResponseTest.java
new file mode 100644
index 0000000..303ab7c
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetAvailableRegionsResponseTest.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.parse;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.jclouds.ultradns.ws.domain.Region;
+import org.jclouds.ultradns.ws.xml.RegionListHandler;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(testName = "GetAvailableRegionsResponseTest")
+public class GetAvailableRegionsResponseTest extends BaseHandlerTest {
+
+   public void test() {
+      InputStream is = getClass().getResourceAsStream("/regions.xml");
+
+      Map<Integer, Region> expected = expected();
+
+      RegionListHandler handler = injector.getInstance(RegionListHandler.class);
+      Map<Integer, Region> result = factory.create(handler).parse(is);
+
+      assertEquals(result.toString(), expected.toString());
+   }
+
+   public Map<Integer, Region> expected() {
+      return ImmutableMap.<Integer, Region> builder()
+                         .put(14, Region.builder()
+                                        .name("Anonymous Proxy (A1)")
+                                        .addTerritoryName("Anonymous Proxy").build())
+                         .put(3, Region.builder()
+                                       .name("Antarctica")
+                                       .territoryNames(ImmutableSet.<String> builder()
+                                                                   .add("Antarctica")
+                                                                   .add("Bouvet Island")
+                                                                   .add("French Southern Territories").build())
+                                       .build())
+                         .build();
+   }
+}
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalDNSGroupDetailsResponseTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalDNSGroupDetailsResponseTest.java
new file mode 100644
index 0000000..85ff3bf
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalDNSGroupDetailsResponseTest.java
@@ -0,0 +1,63 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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 WADirectionalANTIES 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.jclouds.ultradns.ws.parse;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.jclouds.ultradns.ws.domain.DirectionalGroupNameAndRegions;
+import org.jclouds.ultradns.ws.domain.Region;
+import org.jclouds.ultradns.ws.xml.DirectionalGroupNameAndRegionsHandler;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(testName = "GetDirectionalDNSGroupDetailsResponseTest")
+public class GetDirectionalDNSGroupDetailsResponseTest extends BaseHandlerTest {
+
+   public void test() {
+      InputStream is = getClass().getResourceAsStream("/directionalgroup.xml");
+
+      DirectionalGroupNameAndRegions expected = expected();
+
+      DirectionalGroupNameAndRegionsHandler handler = injector.getInstance(DirectionalGroupNameAndRegionsHandler.class);
+      DirectionalGroupNameAndRegions result = factory.create(handler).parse(is);
+
+      assertEquals(result.toString(), expected.toString());
+   }
+
+   public DirectionalGroupNameAndRegions expected() {
+      return DirectionalGroupNameAndRegions.builder()
+                                           .name("NON-EU")
+                                           .addRegion(Region.builder()
+                                                            .name("Anonymous Proxy (A1)")
+                                                            .addTerritoryName("Anonymous Proxy").build())
+                                           .addRegion(Region.builder()
+                                                            .name("Mexico")
+                                                            .addTerritoryName("Mexico").build())
+                                           .addRegion(Region.builder()
+                                                            .name("Antarctica")
+                                                            .addTerritoryName("Bouvet Island")
+                                                            .addTerritoryName("French Southern Territories")
+                                                            .addTerritoryName("Antarctica").build()).build();
+   }
+}
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalDNSRecordsForHostResponseTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalDNSRecordsForHostResponseTest.java
new file mode 100644
index 0000000..a3942c7
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalDNSRecordsForHostResponseTest.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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.jclouds.ultradns.ws.parse;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.jclouds.ultradns.ws.domain.DirectionalGroup;
+import org.jclouds.ultradns.ws.domain.DirectionalRecord;
+import org.jclouds.ultradns.ws.domain.DirectionalRecordDetail;
+import org.jclouds.ultradns.ws.xml.DirectionalRecordDetailListHandler;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(testName = "GetDirectionalDNSRecordsForHostResponseTest")
+public class GetDirectionalDNSRecordsForHostResponseTest extends BaseHandlerTest {
+
+   public void test() {
+      InputStream is = getClass().getResourceAsStream("/directionalrecords.xml");
+
+      FluentIterable<DirectionalRecordDetail> expected = expected();
+
+      DirectionalRecordDetailListHandler handler = injector.getInstance(DirectionalRecordDetailListHandler.class);
+      FluentIterable<DirectionalRecordDetail> result = factory.create(handler).parse(is);
+
+      assertEquals(result.toSet().toString(), expected.toSet().toString());
+   }
+
+   public FluentIterable<DirectionalRecordDetail> expected() {
+      return FluentIterable.from(ImmutableSet.<DirectionalRecordDetail> builder()
+                           .add(DirectionalRecordDetail.builder()
+                                                       .zoneName("directional-example.com.")
+                                                       .name("chaos.directional-example.com.")
+                                                       .id("06093C2D10CB1CB1")
+                                                       .geolocationGroup(DirectionalGroup.builder()
+                                                                                         .id("06093C2D10CB1CB2")
+                                                                                         .name("Geolocation field")
+                                                                                         .build())
+                                                       .sourceIpGroup(DirectionalGroup.builder()
+                                                                                      .id("06093C2D10CB1CB4")
+                                                                                      .name("172.16.1.0/24")
+                                                                                      .build())
+                                                       .record(DirectionalRecord.drBuilder()
+                                                                                .type("A")
+                                                                                .ttl(60)
+                                                                                .noResponseRecord(false)
+                                                                                .rdata("172.16.1.1").build()).build())
+                           .build());
+   }
+
+}
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalPoolsByZoneResponseTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalPoolsByZoneResponseTest.java
new file mode 100644
index 0000000..75f6266
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/parse/GetDirectionalPoolsByZoneResponseTest.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you 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 WADirectionalANTIES 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.jclouds.ultradns.ws.parse;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.InputStream;
+
+import org.jclouds.http.functions.BaseHandlerTest;
+import org.jclouds.ultradns.ws.domain.DirectionalPool;
+import org.jclouds.ultradns.ws.domain.DirectionalPool.TieBreak;
+import org.jclouds.ultradns.ws.domain.DirectionalPool.Type;
+import org.jclouds.ultradns.ws.xml.DirectionalPoolListHandler;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(testName = "GetDirectionalPoolsByZoneResponseTest")
+public class GetDirectionalPoolsByZoneResponseTest extends BaseHandlerTest {
+
+   public void test() {
+      InputStream is = getClass().getResourceAsStream("/directionalpools.xml");
+
+      FluentIterable<DirectionalPool> expected = expected();
+
+      DirectionalPoolListHandler handler = injector.getInstance(DirectionalPoolListHandler.class);
+      FluentIterable<DirectionalPool> result = factory.create(handler).parse(is);
+
+      assertEquals(result.toSet().toString(), expected.toSet().toString());
+   }
+
+   public FluentIterable<DirectionalPool> expected() {
+      return FluentIterable.from(ImmutableList.<DirectionalPool> builder()
+                           .add(DirectionalPool.builder()
+                                               .zoneId("0000000000000001")
+                                               .id("000000000000000A")
+                                               .name("mixy.jclouds.org.")
+                                               .type(Type.MIXED)
+                                               .tieBreak(TieBreak.GEOLOCATION)
+                                               .description("mixy").build())
+                           .add(DirectionalPool.builder()
+                                               .zoneId("0000000000000002")
+                                               .id("000000000000000B")
+                                               .name("geo.jclouds.org.").build()).build());
+   }
+}
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/accountlevelgroup_records.xml b/providers/ultradns-ws/src/test/resources/accountlevelgroup_records.xml
new file mode 100644
index 0000000..bcd3b3c
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/accountlevelgroup_records.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:getDirectionalDNSRecordsForAcctLvlGroupResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<DirectionalDNSRecordDetailList
+				xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">
+				<ns2:DirectionalDNSRecordDetail
+					DirPoolRecordId="06023D94130C8F89" ZoneName="netflix.com."
+					DName="ccs-int-eu.netflix.com.">
+					<ns2:DirectionalDNSRecord recordType="CNAME"
+						TTL="3600" noResponseRecord="false">
+						<ns2:InfoValues
+							Info1Value="ccs-int-frontend-1181536117.eu-west-1.elb.amazonaws.com." />
+					</ns2:DirectionalDNSRecord>
+				</ns2:DirectionalDNSRecordDetail>
+				<ns2:DirectionalDNSRecordDetail
+					DirPoolRecordId="06023D94130C8F61" ZoneName="netflix.com." DName="ccs-int.netflix.com.">
+					<ns2:DirectionalDNSRecord recordType="CNAME"
+						TTL="3600" noResponseRecord="false">
+						<ns2:InfoValues
+							Info1Value="ccs-int-frontend-1181536117.eu-west-1.elb.amazonaws.com." />
+					</ns2:DirectionalDNSRecord>
+				</ns2:DirectionalDNSRecordDetail>
+				<ns2:DirectionalDNSRecordDetail
+					DirPoolRecordId="06023D161000DE02" ZoneName="netflix.com."
+					DName="nrdpv6.nccp.netflix.com.">
+					<ns2:DirectionalDNSRecord recordType="CNAME"
+						TTL="3600" noResponseRecord="false">
+						<ns2:InfoValues
+							Info1Value="dualstack.nccp-wildcard-nccp-netflix-com-1840524669.eu-west-1.elb.amazonaws.com." />
+					</ns2:DirectionalDNSRecord>
+				</ns2:DirectionalDNSRecordDetail>
+			</DirectionalDNSRecordDetailList>
+		</ns1:getDirectionalDNSRecordsForAcctLvlGroupResponse>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/accountlevelgroups.xml b/providers/ultradns-ws/src/test/resources/accountlevelgroups.xml
new file mode 100644
index 0000000..0bd948f
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/accountlevelgroups.xml
@@ -0,0 +1,15 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:getAccountLevelDirectionalGroupsResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<GroupsList xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">
+				<ns2:AccountLevelGroups GroupId="000000000000000A"
+					GroupName="ASIA" RecordsCount="0" GroupType="GEOLOCATION" />
+				<ns2:AccountLevelGroups GroupId="000000000000000B"
+					GroupName="EU" RecordsCount="3" GroupType="GEOLOCATION" />
+				<ns2:AccountLevelGroups GroupId="000000000000000C"
+					GroupName="LATAM" RecordsCount="1" GroupType="GEOLOCATION" />
+			</GroupsList>
+		</ns1:getAccountLevelDirectionalGroupsResponse>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/directionalgroup.xml b/providers/ultradns-ws/src/test/resources/directionalgroup.xml
new file mode 100644
index 0000000..e0dcf52
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/directionalgroup.xml
@@ -0,0 +1,18 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:getDirectionalDNSGroupDetailsResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<DirectionalDNSGroupDetail
+				xmlns:ns2="http://schema.ultraservice.neustar.com/v01/" GroupName="NON-EU">
+				<ns2:DirectionalDNSRegion>
+					<ns2:RegionForNewGroups RegionName="Anonymous Proxy (A1)"
+						TerritoryName="Anonymous Proxy" />
+					<ns2:RegionForNewGroups RegionName="Mexico"
+						TerritoryName="Mexico" />
+					<ns2:RegionForNewGroups RegionName="Antarctica"
+						TerritoryName="Bouvet Island;French Southern Territories;Antarctica" />
+				</ns2:DirectionalDNSRegion>
+			</DirectionalDNSGroupDetail>
+		</ns1:getDirectionalDNSGroupDetailsResponse>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/directionalgroup_doesnt_exist.xml b/providers/ultradns-ws/src/test/resources/directionalgroup_doesnt_exist.xml
new file mode 100644
index 0000000..1052273
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/directionalgroup_doesnt_exist.xml
@@ -0,0 +1,16 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<soap:Fault>
+			<faultcode>soap:Server</faultcode>
+			<faultstring>Fault occurred while processing.</faultstring>
+			<detail>
+				<ns1:UltraWSException xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+					<errorCode xmlns:ns2="http://schema.ultraservice.neustar.com/v01/"
+						xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+						xsi:type="xs:int">4003</errorCode>
+					<errorDescription xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">Group does not exist.</errorDescription>
+				</ns1:UltraWSException>
+			</detail>
+		</soap:Fault>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/directionalgroup_names.xml b/providers/ultradns-ws/src/test/resources/directionalgroup_names.xml
new file mode 100644
index 0000000..a936a75
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/directionalgroup_names.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:getAvailableGroupsResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<DirectionalDNSAvailableGroups
+				xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">
+				<item>EU-foo.jclouds.org.</item>
+				<item>non-foo.jclouds.org.</item>
+			</DirectionalDNSAvailableGroups>
+		</ns1:getAvailableGroupsResponse>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/directionalpool_doesnt_exist.xml b/providers/ultradns-ws/src/test/resources/directionalpool_doesnt_exist.xml
new file mode 100644
index 0000000..19cc7fb
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/directionalpool_doesnt_exist.xml
@@ -0,0 +1,16 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<soap:Fault>
+			<faultcode>soap:Server</faultcode>
+			<faultstring>Fault occurred while processing.</faultstring>
+			<detail>
+				<ns1:UltraWSException xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+					<errorCode xmlns:ns2="http://schema.ultraservice.neustar.com/v01/"
+						xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+						xsi:type="xs:int">2142</errorCode>
+					<errorDescription xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">No Pool or Multiple pools of same type exists for the PoolName : foo.jclouds.org.</errorDescription>
+				</ns1:UltraWSException>
+			</detail>
+		</soap:Fault>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/directionalpools.xml b/providers/ultradns-ws/src/test/resources/directionalpools.xml
new file mode 100644
index 0000000..05206c5
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/directionalpools.xml
@@ -0,0 +1,14 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:getDirectionalPoolsOfZoneResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<DirectionalPoolList xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">
+				<ns2:DirectionalPoolData dirpoolid="000000000000000A"
+					Zoneid="0000000000000001" Pooldname="mixy.jclouds.org."
+					DirPoolType="MIXED" TieBreak="GEOLOCATION" Description="mixy" />
+				<ns2:DirectionalPoolData dirpoolid="000000000000000B"
+					Zoneid="0000000000000002" Pooldname="geo.jclouds.org." />
+			</DirectionalPoolList>
+		</ns1:getDirectionalPoolsOfZoneResponse>
+	</soap:Body>
+</soap:Envelope>
diff --git a/providers/ultradns-ws/src/test/resources/directionalrecords.xml b/providers/ultradns-ws/src/test/resources/directionalrecords.xml
new file mode 100644
index 0000000..43a5475
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/directionalrecords.xml
@@ -0,0 +1,19 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<getDirectionalDNSRecordsForHostResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<DirectionalDNSRecordDetailList
+				ZoneName="directional-example.com." DName="chaos.directional-example.com.">
+				<DirectionalDNSRecordDetail
+					GeolocationGroupName="Geolocation field" GeolocationGroupId="06093C2D10CB1CB2"
+					SourceIPGroupName="172.16.1.0/24" SourceIPGroupId="06093C2D10CB1CB4"
+					TerritoriesCount="8" DirPoolRecordId="06093C2D10CB1CB1">
+					<DirectionalDNSRecord recordType="A" TTL="60"
+						noResponseRecord="false">
+						<InfoValues Info1Value="172.16.1.1" />
+					</DirectionalDNSRecord>
+				</DirectionalDNSRecordDetail>
+			</DirectionalDNSRecordDetailList>
+		</getDirectionalDNSRecordsForHostResponse>
+	</soap:Body>
+</soap:Envelope>
diff --git a/providers/ultradns-ws/src/test/resources/get_directionalgroup.xml b/providers/ultradns-ws/src/test/resources/get_directionalgroup.xml
new file mode 100644
index 0000000..d391d6a
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/get_directionalgroup.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getDirectionalDNSGroupDetails><GroupId>0000000000A</GroupId></v01:getDirectionalDNSGroupDetails></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/group_doesnt_exist.xml b/providers/ultradns-ws/src/test/resources/group_doesnt_exist.xml
new file mode 100644
index 0000000..1052273
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/group_doesnt_exist.xml
@@ -0,0 +1,16 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<soap:Fault>
+			<faultcode>soap:Server</faultcode>
+			<faultstring>Fault occurred while processing.</faultstring>
+			<detail>
+				<ns1:UltraWSException xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+					<errorCode xmlns:ns2="http://schema.ultraservice.neustar.com/v01/"
+						xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+						xsi:type="xs:int">4003</errorCode>
+					<errorDescription xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">Group does not exist.</errorDescription>
+				</ns1:UltraWSException>
+			</detail>
+		</soap:Fault>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/list_accountlevelgroup_records.xml b/providers/ultradns-ws/src/test/resources/list_accountlevelgroup_records.xml
new file mode 100644
index 0000000..51f7159
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/list_accountlevelgroup_records.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getDirectionalDNSRecordsForAcctLvlGroup><groupId>000000000000000A</groupId></v01:getDirectionalDNSRecordsForAcctLvlGroup></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/list_accountlevelgroups.xml b/providers/ultradns-ws/src/test/resources/list_accountlevelgroups.xml
new file mode 100644
index 0000000..a4ea9e2
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/list_accountlevelgroups.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getAccountLevelDirectionalGroups><accountId>accountid</accountId><GroupType /></v01:getAccountLevelDirectionalGroups></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/list_directionalgroup_names.xml b/providers/ultradns-ws/src/test/resources/list_directionalgroup_names.xml
new file mode 100644
index 0000000..2e60121
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/list_directionalgroup_names.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getAvailableGroups><poolName>www.jclouds.org.</poolName><poolRecordType>1</poolRecordType><accountID>accountid</accountID><groupType /></v01:getAvailableGroups></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/list_directionalgroup_records.xml b/providers/ultradns-ws/src/test/resources/list_directionalgroup_records.xml
new file mode 100644
index 0000000..25625c1
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/list_directionalgroup_records.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getDirectionalDNSRecordsForGroup><groupName>EU-www.jclouds.org.</groupName><hostName>www.jclouds.org.</hostName><zoneName>jclouds.org.</zoneName><poolRecordType>1</poolRecordType></v01:getDirectionalDNSRecordsForGroup></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/list_directionalpools.xml b/providers/ultradns-ws/src/test/resources/list_directionalpools.xml
new file mode 100644
index 0000000..782383d
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/list_directionalpools.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getDirectionalPoolsOfZone><zoneName>jclouds.org.</zoneName></v01:getDirectionalPoolsOfZone></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/list_directionalrecords.xml b/providers/ultradns-ws/src/test/resources/list_directionalrecords.xml
new file mode 100644
index 0000000..85fc702
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/list_directionalrecords.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getDirectionalDNSRecordsForHost><zoneName>jclouds.org.</zoneName><hostName>www.jclouds.org.</hostName><poolRecordType>1</poolRecordType></v01:getDirectionalDNSRecordsForHost></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/list_regions.xml b/providers/ultradns-ws/src/test/resources/list_regions.xml
new file mode 100644
index 0000000..e87cecb
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/list_regions.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:getAvailableRegions/></soapenv:Body></soapenv:Envelope>
\ No newline at end of file
diff --git a/providers/ultradns-ws/src/test/resources/regions.xml b/providers/ultradns-ws/src/test/resources/regions.xml
new file mode 100644
index 0000000..d4c646d
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/regions.xml
@@ -0,0 +1,14 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:getAvailableRegionsResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<DirectionalDNSAvailableRegionList
+				xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">
+				<ns2:Region TerritoryName="Anonymous Proxy" RegionName="Anonymous Proxy (A1)"
+					RegionID="14" />
+				<ns2:Region TerritoryName="Antarctica;Bouvet Island;French Southern Territories"
+					RegionName="Antarctica" RegionID="3" />
+			</DirectionalDNSAvailableRegionList>
+		</ns1:getAvailableRegionsResponse>
+	</soap:Body>
+</soap:Envelope>
\ No newline at end of file