You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by an...@apache.org on 2017/01/24 09:45:46 UTC

[1/3] jclouds-labs git commit: Add remaining features to support the abstraction

Repository: jclouds-labs
Updated Branches:
  refs/heads/master dfd6804c8 -> f749c88ec


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/operatingSystems.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/operatingSystems.json b/packet/src/test/resources/operatingSystems.json
new file mode 100644
index 0000000..4fd7645
--- /dev/null
+++ b/packet/src/test/resources/operatingSystems.json
@@ -0,0 +1,166 @@
+{
+  "operating_systems": [
+    {
+      "id": "06e21644-a769-11e6-80f5-76304dec7eb7",
+      "slug": "alpine_3",
+      "name": "Alpine 3",
+      "distro": "alpine",
+      "version": "3",
+      "provisionable_on": []
+    },
+    {
+      "id": "06e21978-a769-11e6-80f5-76304dec7eb7",
+      "slug": "centos_6",
+      "name": "CentOS 6",
+      "distro": "centos",
+      "version": "6",
+      "provisionable_on": []
+    },
+    {
+      "id": "68bad60d-f5a7-45c2-ad09-573edaad3a3c",
+      "slug": "centos_7",
+      "name": "Centos 7",
+      "distro": "centos",
+      "version": "7",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e21ce8-a769-11e6-80f5-76304dec7eb7",
+      "slug": "coreos_alpha",
+      "name": "CoreOS Alpha",
+      "distro": "coreos",
+      "version": "alpha",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "6345f310-0bb0-4b59-b051-954fed05183a",
+      "slug": "coreos_beta",
+      "name": "CoreOS Beta",
+      "distro": "coreos",
+      "version": "beta",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "d61c3912-8422-4daf-835e-854efa0062e4",
+      "slug": "coreos_stable",
+      "name": "CoreOS Stable",
+      "distro": "coreos",
+      "version": "stable",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e21e78-a769-11e6-80f5-76304dec7eb7",
+      "slug": "debian_7",
+      "name": "Debian 7",
+      "distro": "debian",
+      "version": "7",
+      "provisionable_on": []
+    },
+    {
+      "id": "88239019-abc7-41e8-9a4d-cd334da97ff1",
+      "slug": "debian_8",
+      "name": "Debian 8",
+      "distro": "debian",
+      "version": "8",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e21ffe-a769-11e6-80f5-76304dec7eb7",
+      "slug": "deprovision",
+      "name": "Deprovision",
+      "distro": "centos",
+      "version": "",
+      "provisionable_on": []
+    },
+    {
+      "id": "06e22198-a769-11e6-80f5-76304dec7eb7",
+      "slug": "freebsd_10_3",
+      "name": "FreeBSD 10.3",
+      "distro": "freebsd",
+      "version": "10.3",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e22328-a769-11e6-80f5-76304dec7eb7",
+      "slug": "rancher",
+      "name": "RancherOS",
+      "distro": "rancher",
+      "version": "latest",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e224b8-a769-11e6-80f5-76304dec7eb7",
+      "slug": "ubuntu_14_04",
+      "name": "Ubuntu 14.04 LTS",
+      "distro": "ubuntu",
+      "version": "14.04",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+      "slug": "ubuntu_16_04",
+      "name": "Ubuntu 16.04 LTS",
+      "distro": "ubuntu",
+      "version": "16.04",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_2a",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e22972-a769-11e6-80f5-76304dec7eb7",
+      "slug": "windows_2012_rc2",
+      "name": "Windows 2012 RC2",
+      "distro": "windows",
+      "version": "2012 RC2",
+      "provisionable_on": [
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/plans-first.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/plans-first.json b/packet/src/test/resources/plans-first.json
new file mode 100644
index 0000000..cbd502a
--- /dev/null
+++ b/packet/src/test/resources/plans-first.json
@@ -0,0 +1,222 @@
+{
+  "plans": [
+    {
+      "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+      "slug": "baremetal_0",
+      "name": "Type 0",
+      "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 1,
+            "type": "Intel Atom C2550 @ 2.4Ghz"
+          }
+        ],
+        "memory": {
+          "total": "8GB"
+        },
+        "drives": [
+          {
+            "count": 1,
+            "size": "80GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "1Gbps"
+          }
+        ],
+        "features": {
+          "raid": false,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 0.05
+      }
+    },
+    {
+      "id": "6d1f1ffa-7912-4b78-b50d-88cc7c8ab40f",
+      "slug": "baremetal_1",
+      "name": "Type 1",
+      "description": "Our Type 1 configuration is a zippy general use server, with an Intel E3-1240 v3 processor and 32GB of RAM.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 1,
+            "type": "Intel E3-1240 v3"
+          }
+        ],
+        "memory": {
+          "total": "32GB"
+        },
+        "drives": [
+          {
+            "count": 2,
+            "size": "120GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "1Gbps"
+          }
+        ],
+        "features": {
+          "raid": true,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 0.4
+      }
+    },
+    {
+      "id": "a3729923-fdc4-4e85-a972-aafbad3695db",
+      "slug": "baremetal_2",
+      "name": "Type 2",
+      "description": "Our Type 2 configuration is the perfect all purpose virtualization server, with dual E5-2650 v4 processors, 256 GB of DDR4 RAM, and six SSDs totaling 2.8 TB of storage.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 2,
+            "type": "Intel Xeon E5-2650 v4 @2.2GHz"
+          }
+        ],
+        "memory": {
+          "total": "256GB"
+        },
+        "drives": [
+          {
+            "count": 6,
+            "size": "480GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "10Gbps"
+          }
+        ],
+        "features": {
+          "raid": true,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 1.25
+      }
+    },
+    {
+      "id": "3bc8a214-b807-4058-ad4a-6925f2411155",
+      "slug": "baremetal_2a",
+      "name": "Type 2A",
+      "description": "Our Type 2A configuration is a 96-core dual socket ARM 64 beast based on Cavium ThunderX chips",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 2,
+            "type": "Cavium ThunderX CN8890 @2GHz"
+          }
+        ],
+        "memory": {
+          "total": "128GB"
+        },
+        "drives": [
+          {
+            "count": 1,
+            "size": "512GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "10Gbps"
+          }
+        ],
+        "features": {}
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 0.5
+      }
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/plans?page=1"
+    },
+    "previous": null,
+    "self": {
+      "href": "/plans?page=1"
+    },
+    "next": {
+      "href": "/plans?page=2"
+    },
+    "last": {
+      "href": "/plans?page=2"
+    },
+    "total": 7
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/plans-last.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/plans-last.json b/packet/src/test/resources/plans-last.json
new file mode 100644
index 0000000..04d6aee
--- /dev/null
+++ b/packet/src/test/resources/plans-last.json
@@ -0,0 +1,98 @@
+{
+  "plans": [
+    {
+      "id": "741f3afb-bb2f-4694-93a0-fcbad7cd5e78",
+      "slug": "baremetal_3",
+      "name": "Type 3",
+      "description": "Our Type 3 configuration is a high core, high IO server, with dual Intel E5-2640 v3 processors, 128GB of DDR4 RAM and ultra fast NVME flash drives.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 2,
+            "type": "Intel E5-2640 v3"
+          }
+        ],
+        "memory": {
+          "total": "128GB"
+        },
+        "drives": [
+          {
+            "count": 2,
+            "size": "120GB",
+            "type": "SSD"
+          },
+          {
+            "count": 1,
+            "size": "1.6TB",
+            "type": "NVME"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "10Gbps"
+          }
+        ],
+        "features": {
+          "raid": true,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 1.75
+      }
+    },
+    {
+      "id": "87728148-3155-4992-a730-8d1e6aca8a32",
+      "slug": "storage_1",
+      "name": "Standard",
+      "description": "TBD",
+      "line": "storage",
+      "specs": {},
+      "available_in": [],
+      "pricing": {
+        "hour": 0.000104
+      }
+    },
+    {
+      "id": "d6570cfb-38fa-4467-92b3-e45d059bb249",
+      "slug": "storage_2",
+      "name": "Performance",
+      "description": "TBD",
+      "line": "storage",
+      "specs": {},
+      "available_in": [],
+      "pricing": {
+        "hour": 0.000223
+      }
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/plans?page=1"
+    },
+    "previous": {
+      "href": "/plans?page=1"
+    },
+    "self": {
+      "href": "/plans?page=2"
+    },
+    "next": null,
+    "last": {
+      "href": "/plans?page=2"
+    },
+    "total": 7
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/plans.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/plans.json b/packet/src/test/resources/plans.json
new file mode 100644
index 0000000..c3eeedd
--- /dev/null
+++ b/packet/src/test/resources/plans.json
@@ -0,0 +1,284 @@
+{
+  "plans": [
+    {
+      "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+      "slug": "baremetal_0",
+      "name": "Type 0",
+      "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 1,
+            "type": "Intel Atom C2550 @ 2.4Ghz"
+          }
+        ],
+        "memory": {
+          "total": "8GB"
+        },
+        "drives": [
+          {
+            "count": 1,
+            "size": "80GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "1Gbps"
+          }
+        ],
+        "features": {
+          "raid": false,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 0.05
+      }
+    },
+    {
+      "id": "6d1f1ffa-7912-4b78-b50d-88cc7c8ab40f",
+      "slug": "baremetal_1",
+      "name": "Type 1",
+      "description": "Our Type 1 configuration is a zippy general use server, with an Intel E3-1240 v3 processor and 32GB of RAM.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 1,
+            "type": "Intel E3-1240 v3"
+          }
+        ],
+        "memory": {
+          "total": "32GB"
+        },
+        "drives": [
+          {
+            "count": 2,
+            "size": "120GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "1Gbps"
+          }
+        ],
+        "features": {
+          "raid": true,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 0.4
+      }
+    },
+    {
+      "id": "a3729923-fdc4-4e85-a972-aafbad3695db",
+      "slug": "baremetal_2",
+      "name": "Type 2",
+      "description": "Our Type 2 configuration is the perfect all purpose virtualization server, with dual E5-2650 v4 processors, 256 GB of DDR4 RAM, and six SSDs totaling 2.8 TB of storage.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 2,
+            "type": "Intel Xeon E5-2650 v4 @2.2GHz"
+          }
+        ],
+        "memory": {
+          "total": "256GB"
+        },
+        "drives": [
+          {
+            "count": 6,
+            "size": "480GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "10Gbps"
+          }
+        ],
+        "features": {
+          "raid": true,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 1.25
+      }
+    },
+    {
+      "id": "3bc8a214-b807-4058-ad4a-6925f2411155",
+      "slug": "baremetal_2a",
+      "name": "Type 2A",
+      "description": "Our Type 2A configuration is a 96-core dual socket ARM 64 beast based on Cavium ThunderX chips",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 2,
+            "type": "Cavium ThunderX CN8890 @2GHz"
+          }
+        ],
+        "memory": {
+          "total": "128GB"
+        },
+        "drives": [
+          {
+            "count": 1,
+            "size": "512GB",
+            "type": "SSD"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "10Gbps"
+          }
+        ],
+        "features": {}
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 0.5
+      }
+    },
+    {
+      "id": "741f3afb-bb2f-4694-93a0-fcbad7cd5e78",
+      "slug": "baremetal_3",
+      "name": "Type 3",
+      "description": "Our Type 3 configuration is a high core, high IO server, with dual Intel E5-2640 v3 processors, 128GB of DDR4 RAM and ultra fast NVME flash drives.",
+      "line": "baremetal",
+      "specs": {
+        "cpus": [
+          {
+            "count": 2,
+            "type": "Intel E5-2640 v3"
+          }
+        ],
+        "memory": {
+          "total": "128GB"
+        },
+        "drives": [
+          {
+            "count": 2,
+            "size": "120GB",
+            "type": "SSD"
+          },
+          {
+            "count": 1,
+            "size": "1.6TB",
+            "type": "NVME"
+          }
+        ],
+        "nics": [
+          {
+            "count": 2,
+            "type": "10Gbps"
+          }
+        ],
+        "features": {
+          "raid": true,
+          "txt": true
+        }
+      },
+      "available_in": [
+        {
+          "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+        },
+        {
+          "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+        },
+        {
+          "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+        }
+      ],
+      "pricing": {
+        "hour": 1.75
+      }
+    },
+    {
+      "id": "87728148-3155-4992-a730-8d1e6aca8a32",
+      "slug": "storage_1",
+      "name": "Standard",
+      "description": "TBD",
+      "line": "storage",
+      "specs": {},
+      "available_in": [],
+      "pricing": {
+        "hour": 0.000104
+      }
+    },
+    {
+      "id": "d6570cfb-38fa-4467-92b3-e45d059bb249",
+      "slug": "storage_2",
+      "name": "Performance",
+      "description": "TBD",
+      "line": "storage",
+      "specs": {},
+      "available_in": [],
+      "pricing": {
+        "hour": 0.000223
+      }
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/power-off.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/power-off.json b/packet/src/test/resources/power-off.json
new file mode 100644
index 0000000..3cdd43f
--- /dev/null
+++ b/packet/src/test/resources/power-off.json
@@ -0,0 +1,3 @@
+{
+  "type": "power_off"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/power-on.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/power-on.json b/packet/src/test/resources/power-on.json
new file mode 100644
index 0000000..a482c1c
--- /dev/null
+++ b/packet/src/test/resources/power-on.json
@@ -0,0 +1,3 @@
+{
+  "type": "power_on"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/projects.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/projects.json b/packet/src/test/resources/projects.json
index f7f1710..548b4af 100644
--- a/packet/src/test/resources/projects.json
+++ b/packet/src/test/resources/projects.json
@@ -1 +1,77 @@
-{"projects":[{"id":"93907f48-adfe-43ed-ad89-0e6e83721a54","name":"Cloudsoft CCS Testing","created_at":"2016-09-15T08:50:58Z","updated_at":"2017-01-05T09:36:53Z","max_devices":{"baremetal_0":null,"baremetal_1":null,"baremetal_2":null,"baremetal_3":null,"baremetal_2a":null,"storage_1":null,"storage_2":null},"members":[{"href":"/users/1140617d-262d-4502-a3d6-771d83c930da"},{"href":"/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd"},{"href":"/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e"},{"href":"/users/ad711bc3-6333-449a-a405-23ca81f38c00"}],"memberships":[{"href":"/memberships/914facae-547f-46fc-93e8-860eb53d9bf6"},{"href":"/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69"},{"href":"/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d"},{"href":"/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec"}],"invitations":[],"payment_method":{"href":"/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5"},"devices":[{"href":"/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df"}],"ssh_keys":[{"href":"/ssh-keys
 /070e3282-5b6a-4f75-8f18-a4e7488eafaa"}],"volumes":[],"href":"/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"}],"meta":{"first":{"href":"/projects?page=1"},"previous":null,"self":{"href":"/projects?page=1"},"next":null,"last":{"href":"/projects?page=1"},"total":1}}
+{
+  "projects": [
+    {
+      "id": "93907f48-adfe-43ed-ad89-0e6e83721a54",
+      "name": "Cloudsoft CCS Testing",
+      "created_at": "2016-09-15T08:50:58Z",
+      "updated_at": "2017-01-05T09:36:53Z",
+      "max_devices": {
+        "baremetal_0": null,
+        "baremetal_1": null,
+        "baremetal_2": null,
+        "baremetal_3": null,
+        "baremetal_2a": null,
+        "storage_1": null,
+        "storage_2": null
+      },
+      "members": [
+        {
+          "href": "/users/1140617d-262d-4502-a3d6-771d83c930da"
+        },
+        {
+          "href": "/users/343345fe-18b3-46a3-9b9c-e4a2fe88ccbd"
+        },
+        {
+          "href": "/users/73b0442e-cc4b-42a0-8d3a-c8dfb8a4ff2e"
+        },
+        {
+          "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+        }
+      ],
+      "memberships": [
+        {
+          "href": "/memberships/914facae-547f-46fc-93e8-860eb53d9bf6"
+        },
+        {
+          "href": "/memberships/1ce7c9d9-d11f-47f2-b5a6-d1221338ad69"
+        },
+        {
+          "href": "/memberships/e25c4478-9e44-465a-a9d7-bffc7a83300d"
+        },
+        {
+          "href": "/memberships/8a00c05b-3ddc-41c3-8dd7-4a3d5984ecec"
+        }
+      ],
+      "invitations": [],
+      "payment_method": {
+        "href": "/payment-methods/b2bb4ee0-506b-4c01-b17d-ba41d5c430c5"
+      },
+      "devices": [
+        {
+          "href": "/devices/4942a6c7-55c6-42cc-a06b-cccd6f2fa5df"
+        }
+      ],
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/070e3282-5b6a-4f75-8f18-a4e7488eafaa"
+        }
+      ],
+      "volumes": [],
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/projects?page=1"
+    },
+    "previous": null,
+    "self": {
+      "href": "/projects?page=1"
+    },
+    "next": null,
+    "last": {
+      "href": "/projects?page=1"
+    },
+    "total": 1
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/reboot.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/reboot.json b/packet/src/test/resources/reboot.json
new file mode 100644
index 0000000..9bf6dfd
--- /dev/null
+++ b/packet/src/test/resources/reboot.json
@@ -0,0 +1,3 @@
+{
+  "type": "reboot"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/rescue.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/rescue.json b/packet/src/test/resources/rescue.json
new file mode 100644
index 0000000..91ea877
--- /dev/null
+++ b/packet/src/test/resources/rescue.json
@@ -0,0 +1,3 @@
+{
+  "type": "rescue"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/ssh-key-create-req.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/ssh-key-create-req.json b/packet/src/test/resources/ssh-key-create-req.json
new file mode 100644
index 0000000..02bdeff
--- /dev/null
+++ b/packet/src/test/resources/ssh-key-create-req.json
@@ -0,0 +1,4 @@
+{
+  "label": "jclouds-ssh-key-livetest",
+  "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCdgcoNzH4hCc0j3b4MuG503L/J54uyFvwCAOu8vSsYuLpJ4AEyEOv+T0SfdF605fK6GYXA16Rxk3lrPt7mfKGNtXR0Ripbv7Zc6PvCRorwgj/cjh/45miozjrkXAiHD1GFZycfbi4YsoWAqZj7W4mwtctmhrYM0FPdya2XoRpVy89N+A5Xo4Xtd6EZn6JGEKQM5+kF2aL3ggy0od/DqjuEVYwZoyTe1RgUTXZSU/Woh7WMhsRHbqd3eYz4s6ac8n8IJPGKtUaQeqUtH7OK6NRYXVypUrkqNlwdNYZAwrjXg/x5T3D+bo11LENASRt9OJ2OkmRSTqRxBeDkhnVauWK/"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/ssh-key-create-res.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/ssh-key-create-res.json b/packet/src/test/resources/ssh-key-create-res.json
new file mode 100644
index 0000000..fa386fb
--- /dev/null
+++ b/packet/src/test/resources/ssh-key-create-res.json
@@ -0,0 +1,12 @@
+{
+  "id": "da5d6c21-2e8c-43ac-820f-ff41bd4e6ebc",
+  "label": "andrea-device-livetest",
+  "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCdgcoNzH4hCc0j3b4MuG503L/J54uyFvwCAOu8vSsYuLpJ4AEyEOv+T0SfdF605fK6GYXA16Rxk3lrPt7mfKGNtXR0Ripbv7Zc6PvCRorwgj/cjh/45miozjrkXAiHD1GFZycfbi4YsoWAqZj7W4mwtctmhrYM0FPdya2XoRpVy89N+A5Xo4Xtd6EZn6JGEKQM5+kF2aL3ggy0od/DqjuEVYwZoyTe1RgUTXZSU/Woh7WMhsRHbqd3eYz4s6ac8n8IJPGKtUaQeqUtH7OK6NRYXVypUrkqNlwdNYZAwrjXg/x5T3D+bo11LENASRt9OJ2OkmRSTqRxBeDkhnVauWK/",
+  "fingerprint": "60:b0:5a:23:42:b8:53:19:d0:32:f1:4d:a8:7c:9b:71",
+  "created_at": "2017-01-20T14:15:26Z",
+  "updated_at": "2017-01-20T14:15:26Z",
+  "owner": {
+    "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+  },
+  "href": "/ssh-keys/da5d6c21-2e8c-43ac-820f-ff41bd4e6ebc"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/ssh-key.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/ssh-key.json b/packet/src/test/resources/ssh-key.json
new file mode 100644
index 0000000..f3f21ea
--- /dev/null
+++ b/packet/src/test/resources/ssh-key.json
@@ -0,0 +1,12 @@
+{
+  "id": "bba63e41-b12c-493a-81d4-e52f50f247ed",
+  "label": "test",
+  "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+  "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+  "created_at": "2017-01-03T09:42:54Z",
+  "updated_at": "2017-01-03T09:42:54Z",
+  "owner": {
+    "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+  },
+  "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/sshKeys-first.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/sshKeys-first.json b/packet/src/test/resources/sshKeys-first.json
new file mode 100644
index 0000000..1f0b9b3
--- /dev/null
+++ b/packet/src/test/resources/sshKeys-first.json
@@ -0,0 +1,80 @@
+{
+  "ssh_keys": [
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e1",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    },
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e2",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    },
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e3",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    },
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e4",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    },
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e5",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/ssh-keys?page=1"
+    },
+    "previous": null,
+    "self": {
+      "href": "/ssh-keys?page=1"
+    },
+    "next": {
+      "href": "/ssh-keys?page=2"
+    },
+    "last": {
+      "href": "/ssh-keys?page=2"
+    },
+    "total": 8
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/sshKeys-last.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/sshKeys-last.json b/packet/src/test/resources/sshKeys-last.json
new file mode 100644
index 0000000..15fd116
--- /dev/null
+++ b/packet/src/test/resources/sshKeys-last.json
@@ -0,0 +1,56 @@
+{
+  "ssh_keys": [
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e6",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    },
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e7",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    },
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247e8",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/ssh-keys?page=1"
+    },
+    "previous": {
+      "href": "/ssh-keys?page=1"
+    },
+    "self": {
+      "href": "/ssh-keys?page=2"
+    },
+    "next": null,
+    "last": {
+      "href": "/ssh-keys?page=2"
+    },
+    "total": 8
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/sshKeys.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/sshKeys.json b/packet/src/test/resources/sshKeys.json
new file mode 100644
index 0000000..582c730
--- /dev/null
+++ b/packet/src/test/resources/sshKeys.json
@@ -0,0 +1,16 @@
+{
+  "ssh_keys": [
+    {
+      "id": "bba63e41-b12c-493a-81d4-e52f50f247ed",
+      "label": "test",
+      "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNnvPZ25wZK19grrbal6R5JP1hLRBUuNh86KxZVMAFkGd5ouVSgO9dFajHZ45Q4mbaTkdOiqf7otMLDzkaztEa7oLK7Jso0Y0LOi+nT4gf38rvbEF5mq069G9b9XqlNleaGnpLuTN54iEK8c4TWZxIJqgelHEHhjp7V1asmilBbpZDmwA5cTt9vGJIhqA/BptKH3folZKeAQjRa1ZRSwSQUnk9rBKn4PKSRDojVa9A9jKt4qboJh5q7ZjtE8z+665F/4TgLzElTXUA8+uUFGpuynMSmQEt301e18dXAl+vBr8fMiThcoVVbdVdqdjXsw75fMXPAgqhjrw8k3+0/4P9 andrea@bigmac.local",
+      "fingerprint": "88:8a:7d:0f:db:fb:f4:2a:63:2e:d3:4b:1f:7f:0b:12",
+      "created_at": "2017-01-03T09:42:54Z",
+      "updated_at": "2017-01-03T09:42:54Z",
+      "owner": {
+        "href": "/users/ad711bc3-6333-449a-a405-23ca81f38c00"
+      },
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/user.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/user.json b/packet/src/test/resources/user.json
new file mode 100644
index 0000000..a3c4677
--- /dev/null
+++ b/packet/src/test/resources/user.json
@@ -0,0 +1,19 @@
+{
+  "id": "faaaec30-be0c-4b6d-b1a7-1bf6e494a333",
+  "first_name": "Melissa",
+  "last_name": "Smith",
+  "full_name": "Melissa Smith",
+  "email": "melissa@example.com",
+  "max_projects": 1,
+  "two_factor_auth": "sms",
+  "timezone": "America/New_York",
+  "phone_number": "555-555-5555",
+  "created_at": "2014-04-14T02:15:15Z",
+  "updated_at": "2014-04-14T02:15:15Z",
+  "timezone": "America/New_York",
+  "emails": [
+    { "href": "/emails/7e407a6a-4f96-4da0-8c48-5665352ebf06" }
+  ],
+  "avatar_url": "https://www.gravatar.com/avatar/d248bbb3a03ed?d=https://app.packet.net/client-portal/assets/images/default_gravatar.png",
+  "href": "/users/faaaec30-be0c-4b6d-b1a7-1bf6e494a333"
+}


[2/3] jclouds-labs git commit: Add remaining features to support the abstraction

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/SshKeyApiLiveTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/SshKeyApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/SshKeyApiLiveTest.java
new file mode 100644
index 0000000..3ef4387
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/SshKeyApiLiveTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.packet.compute.internal.BasePacketApiLiveTest;
+import org.jclouds.packet.domain.SshKey;
+import org.jclouds.ssh.SshKeys;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "SshKeyApiLiveTest")
+public class SshKeyApiLiveTest extends BasePacketApiLiveTest {
+
+   private SshKey sshKey;
+
+   public void testCreate() {
+      Map<String, String> keyPair = SshKeys.generate();
+      sshKey = api.sshKeyApi().create(prefix + "-sshkey-livetest", keyPair.get("public"));
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testGet() {
+      api.sshKeyApi().get(sshKey.id());
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list().concat(), new Predicate<SshKey>() {
+         @Override
+         public boolean apply(SshKey input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All ssh keys must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some ssh keys to be returned");
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testListOnePage() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<SshKey>() {
+         @Override
+         public boolean apply(SshKey input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All ssh keys must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some ssh keys to be returned");
+   }
+
+   @Test(dependsOnMethods = "testList", alwaysRun = true)
+   public void testDelete() throws InterruptedException {
+      api.sshKeyApi().delete(sshKey.id());
+   }
+
+   private SshKeyApi api() {
+      return api.sshKeyApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/SshKeyApiMockTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/SshKeyApiMockTest.java b/packet/src/test/java/org/jclouds/packet/features/SshKeyApiMockTest.java
new file mode 100644
index 0000000..3bb1969
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/SshKeyApiMockTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
+import org.jclouds.packet.domain.SshKey;
+import org.testng.annotations.Test;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "SshKeyApiMockTest", singleThreaded = true)
+public class SshKeyApiMockTest extends BasePacketApiMockTest {
+
+   public void testListSshKeys() throws InterruptedException {
+      server.enqueue(jsonResponse("/sshKeys-first.json"));
+      server.enqueue(jsonResponse("/sshKeys-last.json"));
+
+      Iterable<SshKey> sshkeys = api.sshKeyApi().list().concat();
+
+      assertEquals(size(sshkeys), 8); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/ssh-keys");
+      assertSent(server, "GET", "/ssh-keys?page=2");
+   }
+
+   public void testListSshKeysReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<SshKey> sshkeys = api.sshKeyApi().list().concat();
+
+      assertTrue(isEmpty(sshkeys));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/ssh-keys");
+   }
+
+   public void testListSshKeysWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/sshKeys-first.json"));
+
+      Iterable<SshKey> actions = api.sshKeyApi().list(page(1).perPage(5));
+
+      assertEquals(size(actions), 5);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/ssh-keys?page=1&per_page=5");
+   }
+
+   public void testListSshKeysWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<SshKey> actions = api.sshKeyApi().list(page(1).perPage(5));
+
+      assertTrue(isEmpty(actions));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/ssh-keys?page=1&per_page=5");
+   }
+
+   public void testGetSshKey() throws InterruptedException {
+      server.enqueue(jsonResponse("/ssh-key.json"));
+
+      SshKey sshKey = api.sshKeyApi().get("1");
+
+      assertEquals(sshKey, objectFromResource("/ssh-key.json", SshKey.class));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/ssh-keys/1");
+   }
+
+   public void testGetSshKeyReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      SshKey sshKey = api.sshKeyApi().get("1");
+
+      assertNull(sshKey);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/ssh-keys/1");
+   }
+
+   public void testCreateSshKey() throws InterruptedException {
+      server.enqueue(jsonResponse("/ssh-key-create-res.json"));
+
+      SshKey sshKey = api.sshKeyApi().create(
+              "jclouds-ssh-key-livetest",
+              "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCdgcoNzH4hCc0j3b4MuG503L/J54uyFvwCAOu8vSsYuLpJ4AEyEOv+T0SfdF605fK6GYXA16Rxk3lrPt7mfKGNtXR0Ripbv7Zc6PvCRorwgj/cjh/45miozjrkXAiHD1GFZycfbi4YsoWAqZj7W4mwtctmhrYM0FPdya2XoRpVy89N+A5Xo4Xtd6EZn6JGEKQM5+kF2aL3ggy0od/DqjuEVYwZoyTe1RgUTXZSU/Woh7WMhsRHbqd3eYz4s6ac8n8IJPGKtUaQeqUtH7OK6NRYXVypUrkqNlwdNYZAwrjXg/x5T3D+bo11LENASRt9OJ2OkmRSTqRxBeDkhnVauWK/"
+      );
+
+      assertEquals(sshKey, objectFromResource("/ssh-key-create-res.json", SshKey.class));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/ssh-keys", stringFromResource("/ssh-key-create-req.json"));
+   }
+
+   public void testDeleteSshKey() throws InterruptedException {
+      server.enqueue(response204());
+
+      api.sshKeyApi().delete("1");
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/ssh-keys/1");
+   }
+
+   public void testDeleteSshKeyReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      api.sshKeyApi().delete("1");
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/ssh-keys/1");
+   }
+
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/functions/HrefToListOptionsTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/functions/HrefToListOptionsTest.java b/packet/src/test/java/org/jclouds/packet/functions/HrefToListOptionsTest.java
new file mode 100644
index 0000000..1fecb29
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/functions/HrefToListOptionsTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.functions;
+
+import org.jclouds.packet.domain.Href;
+import org.jclouds.packet.domain.options.ListOptions;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Multimap;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.jclouds.packet.domain.options.ListOptions.PAGE_PARAM;
+import static org.jclouds.packet.domain.options.ListOptions.PER_PAGE_PARAM;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+@Test(groups = "unit", testName = "HrefToListOptionsTest")
+public class HrefToListOptionsTest {
+
+   public void testNoOptions() {
+      HrefToListOptions function = new HrefToListOptions();
+
+      ListOptions options = function.apply(Href.create("https://api.packet.net/projects"));
+      assertNotNull(options);
+
+      Multimap<String, String> params = options.buildQueryParameters();
+      assertFalse(params.containsKey(PAGE_PARAM));
+      assertFalse(params.containsKey(PER_PAGE_PARAM));
+   }
+
+   public void testWithOptions() {
+      HrefToListOptions function = new HrefToListOptions();
+
+      ListOptions options = function.apply(Href.create("https://api.packet.net/projects?page=2&per_page=5"));
+      assertNotNull(options);
+
+      Multimap<String, String> params = options.buildQueryParameters();
+      assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "2");
+      assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java b/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java
deleted file mode 100644
index 15262e8..0000000
--- a/packet/src/test/java/org/jclouds/packet/functions/LinkToListOptionsTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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.packet.functions;
-
-import org.jclouds.packet.domain.Href;
-import org.jclouds.packet.domain.options.ListOptions;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.Multimap;
-
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static org.jclouds.packet.domain.options.ListOptions.PAGE_PARAM;
-import static org.jclouds.packet.domain.options.ListOptions.PER_PAGE_PARAM;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-
-@Test(groups = "unit", testName = "LinkToListOptionsTest")
-public class LinkToListOptionsTest {
-
-   public void testNoOptions() {
-      LinkToListOptions function = new LinkToListOptions();
-
-      ListOptions options = function.apply(Href.create("https://api.packet.net/projects"));
-      assertNotNull(options);
-
-      Multimap<String, String> params = options.buildQueryParameters();
-      assertFalse(params.containsKey(PAGE_PARAM));
-      assertFalse(params.containsKey(PER_PAGE_PARAM));
-   }
-
-   public void testWithOptions() {
-      LinkToListOptions function = new LinkToListOptions();
-
-      ListOptions options = function.apply(Href.create("https://api.packet.net/projects?page=2&per_page=5"));
-      assertNotNull(options);
-
-      Multimap<String, String> params = options.buildQueryParameters();
-      assertEquals(getOnlyElement(params.get(PAGE_PARAM)), "2");
-      assertEquals(getOnlyElement(params.get(PER_PAGE_PARAM)), "5");
-   }
-
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/device-create-req.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/device-create-req.json b/packet/src/test/resources/device-create-req.json
new file mode 100644
index 0000000..78e5ea5
--- /dev/null
+++ b/packet/src/test/resources/device-create-req.json
@@ -0,0 +1,11 @@
+{
+  "hostname": "jclouds-device-livetest",
+  "plan": "baremetal_0",
+  "billing_cycle": "hourly",
+  "facility": "ewr1",
+  "features": {},
+  "operating_system": "ubuntu_16_04",
+  "locked": false,
+  "userdata": "",
+  "tags": []
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/device-create-res.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/device-create-res.json b/packet/src/test/resources/device-create-res.json
new file mode 100644
index 0000000..7a5986f
--- /dev/null
+++ b/packet/src/test/resources/device-create-res.json
@@ -0,0 +1,211 @@
+{
+  "id": "3238ce59-fe02-4b9b-92c1-915ef4f3fb9e",
+  "short_id": "3238ce59",
+  "hostname": "andrea-device-livetest",
+  "description": null,
+  "state": "queued",
+  "tags": [],
+  "billing_cycle": "hourly",
+  "user": "root",
+  "iqn": "iqn.2017-01.net.packet:device.3238ce59",
+  "locked": false,
+  "bonding_mode": 5,
+  "created_at": "2017-01-20T14:15:28Z",
+  "updated_at": "2017-01-20T14:15:29Z",
+  "provisioning_percentage": 10.0,
+  "operating_system": {
+    "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+    "slug": "ubuntu_16_04",
+    "name": "Ubuntu 16.04 LTS",
+    "distro": "ubuntu",
+    "version": "16.04",
+    "provisionable_on": [
+      "baremetal_0",
+      "baremetal_1",
+      "baremetal_2",
+      "baremetal_2a",
+      "baremetal_3",
+      "baremetal_hua"
+    ]
+  },
+  "facility": {
+    "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+    "name": "Parsippany, NJ",
+    "code": "ewr1",
+    "features": [
+      "baremetal",
+      "storage"
+    ],
+    "address": null
+  },
+  "project": {
+    "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+  },
+  "ssh_keys": [
+    {
+      "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+    },
+    {
+      "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+    },
+    {
+      "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+    },
+    {
+      "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+    },
+    {
+      "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+    },
+    {
+      "href": "/ssh-keys/da5d6c21-2e8c-43ac-820f-ff41bd4e6ebc"
+    }
+  ],
+  "project_lite": {
+    "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+  },
+  "volumes": [],
+  "ip_addresses": [],
+  "provisioning_events": [
+    {
+      "id": "32a4da09-37dd-446b-99ef-9f64c1eb0097",
+      "type": "provisioning.101",
+      "body": "Provisioning started",
+      "created_at": "2017-01-20T14:15:29Z",
+      "relationships": [
+        {
+          "href": "#5062a5fe-19ea-4b41-864c-e5f4dcb02fa0"
+        }
+      ],
+      "interpolated": "Provisioning started",
+      "href": "/events/32a4da09-37dd-446b-99ef-9f64c1eb0097"
+    },
+    {
+      "id": null,
+      "type": "provisioning.102",
+      "body": "Network configured",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Network configured"
+    },
+    {
+      "id": null,
+      "type": "provisioning.103",
+      "body": "Configuration written, restarting device",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Configuration written, restarting device"
+    },
+    {
+      "id": null,
+      "type": "provisioning.104",
+      "body": "Connected to magic install system",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Connected to magic install system"
+    },
+    {
+      "id": null,
+      "type": "provisioning.105",
+      "body": "Server partitions created",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Server partitions created"
+    },
+    {
+      "id": null,
+      "type": "provisioning.106",
+      "body": "Operating system packages installed",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Operating system packages installed"
+    },
+    {
+      "id": null,
+      "type": "provisioning.107",
+      "body": "Server networking interfaces configured",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Server networking interfaces configured"
+    },
+    {
+      "id": null,
+      "type": "provisioning.108",
+      "body": "Cloud-init packages installed and configured",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Cloud-init packages installed and configured"
+    },
+    {
+      "id": null,
+      "type": "provisioning.109",
+      "body": "Installation finished, rebooting server",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Installation finished, rebooting server"
+    },
+    {
+      "id": null,
+      "type": "provisioning.109",
+      "body": "Installation finished, rebooting server",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Installation finished, rebooting server"
+    }
+  ],
+  "plan": {
+    "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+    "slug": "baremetal_0",
+    "name": "Type 0",
+    "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+    "line": "baremetal",
+    "specs": {
+      "cpus": [
+        {
+          "count": 1,
+          "type": "Intel Atom C2550 @ 2.4Ghz"
+        }
+      ],
+      "memory": {
+        "total": "8GB"
+      },
+      "drives": [
+        {
+          "count": 1,
+          "size": "80GB",
+          "type": "SSD"
+        }
+      ],
+      "nics": [
+        {
+          "count": 2,
+          "type": "1Gbps"
+        }
+      ],
+      "features": {
+        "raid": false,
+        "txt": true
+      }
+    },
+    "available_in": [
+      {
+        "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+      },
+      {
+        "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+      },
+      {
+        "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+      },
+      {
+        "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+      }
+    ],
+    "pricing": {
+      "hour": 0.05
+    }
+  },
+  "userdata": "",
+  "root_password": "$n4hwka3!i",
+  "href": "/devices/3238ce59-fe02-4b9b-92c1-915ef4f3fb9e"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/device.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/device.json b/packet/src/test/resources/device.json
new file mode 100644
index 0000000..b4daa39
--- /dev/null
+++ b/packet/src/test/resources/device.json
@@ -0,0 +1,278 @@
+{
+  "id": "98e22032-579e-4c04-bb12-05cc6a3864c8",
+  "short_id": "98e22032",
+  "hostname": "test",
+  "description": null,
+  "state": "provisioning",
+  "tags": [],
+  "billing_cycle": "hourly",
+  "user": "root",
+  "iqn": "iqn.2017-01.net.packet:device.98e22032",
+  "locked": false,
+  "bonding_mode": 5,
+  "created_at": "2017-01-03T09:47:59Z",
+  "updated_at": "2017-01-03T09:50:13Z",
+  "provisioning_percentage": 50,
+  "operating_system": {
+    "id": "68bad60d-f5a7-45c2-ad09-573edaad3a3c",
+    "slug": "centos_7",
+    "name": "Centos 7",
+    "distro": "centos",
+    "version": "7",
+    "provisionable_on": [
+      "baremetal_0",
+      "baremetal_1",
+      "baremetal_2",
+      "baremetal_3"
+    ]
+  },
+  "facility": {
+    "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+    "name": "Parsippany, NJ",
+    "code": "ewr1",
+    "features": [
+      "baremetal",
+      "storage"
+    ],
+    "address": null
+  },
+  "project": {
+    "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+  },
+  "ssh_keys": [
+    {
+      "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+    },
+    {
+      "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+    },
+    {
+      "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+    },
+    {
+      "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+    },
+    {
+      "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+    }
+  ],
+  "project_lite": {
+    "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+  },
+  "volumes": [],
+  "ip_addresses": [
+    {
+      "id": "5d0262c7-1727-411c-94c4-9e6f15490dd3",
+      "address_family": 4,
+      "netmask": "255.255.255.254",
+      "created_at": "2017-01-03T09:47:59Z",
+      "public": true,
+      "cidr": 31,
+      "management": true,
+      "manageable": true,
+      "assigned_to": {
+        "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+      },
+      "network": "147.75.106.80",
+      "address": "147.75.106.81",
+      "gateway": "147.75.106.80",
+      "href": "/ips/5d0262c7-1727-411c-94c4-9e6f15490dd3"
+    },
+    {
+      "id": "f7d0e65c-eb3b-42bd-af9b-ad3a736d8d43",
+      "address_family": 6,
+      "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+      "created_at": "2017-01-03T09:47:59Z",
+      "public": true,
+      "cidr": 127,
+      "management": true,
+      "manageable": true,
+      "assigned_to": {
+        "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+      },
+      "network": "2604:1380:2:9800::",
+      "address": "2604:1380:2:9800::1",
+      "gateway": "2604:1380:2:9800::",
+      "href": "/ips/f7d0e65c-eb3b-42bd-af9b-ad3a736d8d43"
+    },
+    {
+      "id": "a3d00b4e-d74f-4ac2-8bc9-91065d815b41",
+      "address_family": 4,
+      "netmask": "255.255.255.254",
+      "created_at": "2017-01-03T09:47:59Z",
+      "public": false,
+      "cidr": 31,
+      "management": true,
+      "manageable": true,
+      "assigned_to": {
+        "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+      },
+      "network": "10.99.214.0",
+      "address": "10.99.214.1",
+      "gateway": "10.99.214.0",
+      "href": "/ips/a3d00b4e-d74f-4ac2-8bc9-91065d815b41"
+    }
+  ],
+  "provisioning_events": [
+    {
+      "id": "bd62123b-afed-4e54-b1b9-89e219ba9cf0",
+      "type": "provisioning.101",
+      "body": "Provisioning started",
+      "created_at": "2017-01-03T09:47:59Z",
+      "relationships": [
+        {
+          "href": "#81909921-255e-413c-883a-c58d14c801ae"
+        }
+      ],
+      "interpolated": "Provisioning started",
+      "href": "/events/bd62123b-afed-4e54-b1b9-89e219ba9cf0"
+    },
+    {
+      "id": "14b4a9e6-be90-40ee-be48-b272f855e39c",
+      "type": "provisioning.102",
+      "body": "Network configured with addresses 147.75.106.81, 2604:1380:2:9800::1, and 10.99.214.1",
+      "created_at": "2017-01-03T09:48:46Z",
+      "relationships": [
+        {
+          "href": "#28588657-b8bf-44a1-98d3-27f8e04b660a"
+        }
+      ],
+      "interpolated": "Network configured with addresses 147.75.106.81, 2604:1380:2:9800::1, and 10.99.214.1",
+      "href": "/events/14b4a9e6-be90-40ee-be48-b272f855e39c"
+    },
+    {
+      "id": "57e3cadb-f9aa-4c73-be54-3c83e6cf462e",
+      "type": "provisioning.103",
+      "body": "Configuration written, restarting device",
+      "created_at": "2017-01-03T09:48:57Z",
+      "relationships": [
+        {
+          "href": "#25a7e807-f4aa-4f36-83cd-8347baeb26bc"
+        }
+      ],
+      "interpolated": "Configuration written, restarting device",
+      "href": "/events/57e3cadb-f9aa-4c73-be54-3c83e6cf462e"
+    },
+    {
+      "id": "b8322996-f57e-4c87-96cc-a16f33a0c305",
+      "type": "provisioning.104",
+      "body": "Connected to magic install system",
+      "created_at": "2017-01-03T09:50:13Z",
+      "relationships": [
+        {
+          "href": "#a4825d20-f7f2-426a-88db-38696bd3dfd6"
+        }
+      ],
+      "interpolated": "Connected to magic install system",
+      "href": "/events/b8322996-f57e-4c87-96cc-a16f33a0c305"
+    },
+    {
+      "id": "f00a00a9-7c59-420d-bdcf-2c0993303cf6",
+      "type": "provisioning.105",
+      "body": "Server partitions created",
+      "created_at": "2017-01-03T09:50:13Z",
+      "relationships": [
+        {
+          "href": "#6140659b-5e3d-4686-be8f-8879b6d3e27f"
+        }
+      ],
+      "interpolated": "Server partitions created",
+      "href": "/events/f00a00a9-7c59-420d-bdcf-2c0993303cf6"
+    },
+    {
+      "id": null,
+      "type": "provisioning.106",
+      "body": "Operating system packages installed",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Operating system packages installed"
+    },
+    {
+      "id": null,
+      "type": "provisioning.107",
+      "body": "Server networking interfaces configured",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Server networking interfaces configured"
+    },
+    {
+      "id": null,
+      "type": "provisioning.108",
+      "body": "Cloud-init packages installed and configured",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Cloud-init packages installed and configured"
+    },
+    {
+      "id": null,
+      "type": "provisioning.109",
+      "body": "Installation finished, rebooting server",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Installation finished, rebooting server"
+    },
+    {
+      "id": null,
+      "type": "provisioning.109",
+      "body": "Installation finished, rebooting server",
+      "created_at": null,
+      "relationships": [],
+      "interpolated": "Installation finished, rebooting server"
+    }
+  ],
+  "plan": {
+    "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+    "slug": "baremetal_0",
+    "name": "Type 0",
+    "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+    "line": "baremetal",
+    "specs": {
+      "cpus": [
+        {
+          "count": 1,
+          "type": "Intel Atom C2550 @ 2.4Ghz"
+        }
+      ],
+      "memory": {
+        "total": "8GB"
+      },
+      "drives": [
+        {
+          "count": 1,
+          "size": "80GB",
+          "type": "SSD"
+        }
+      ],
+      "nics": [
+        {
+          "count": 2,
+          "type": "1Gbps"
+        }
+      ],
+      "features": {
+        "raid": false,
+        "txt": true
+      }
+    },
+    "available_in": [
+      {
+        "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+      },
+      {
+        "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+      },
+      {
+        "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+      },
+      {
+        "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+      }
+    ],
+    "pricing": {
+      "hour": 0.05
+    }
+  },
+  "userdata": "",
+  "root_password": ",q4*a8(eny",
+  "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/devices-first.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/devices-first.json b/packet/src/test/resources/devices-first.json
new file mode 100644
index 0000000..514c0eb
--- /dev/null
+++ b/packet/src/test/resources/devices-first.json
@@ -0,0 +1,910 @@
+{
+  "devices": [
+    {
+      "id": "5bd27259-8b71-48f7-879d-c4e695cb2f31",
+      "short_id": "5bd27259",
+      "hostname": "andrea-device-livetest",
+      "description": null,
+      "state": "active",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.5bd27259",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-18T17:12:29Z",
+      "updated_at": "2017-01-18T17:16:29Z",
+      "operating_system": {
+        "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+        "slug": "ubuntu_16_04",
+        "name": "Ubuntu 16.04 LTS",
+        "distro": "ubuntu",
+        "version": "16.04",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_2a",
+          "baremetal_3",
+          "baremetal_hua"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+        },
+        {
+          "href": "/ssh-keys/cf9db551-ec79-4c16-a776-97cdbcb3cec4"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "cbff502b-12ca-4986-b683-2cb4e119ae40",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "147.75.197.54",
+          "address": "147.75.197.55",
+          "gateway": "147.75.197.54",
+          "href": "/ips/cbff502b-12ca-4986-b683-2cb4e119ae40"
+        },
+        {
+          "id": "d5cec621-18c1-4e02-8f71-8dd46370c3e5",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/d5cec621-18c1-4e02-8f71-8dd46370c3e5"
+        },
+        {
+          "id": "6d206311-1f88-454d-9cf2-afca79f51fa3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/6d206311-1f88-454d-9cf2-afca79f51fa3"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": "soaz0#a=cd",
+      "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+    },
+    {
+      "id": "5bd27259-8b71-48f7-879d-c4e695cb2f32",
+      "short_id": "5bd27259",
+      "hostname": "andrea-device-livetest",
+      "description": null,
+      "state": "active",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.5bd27259",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-18T17:12:29Z",
+      "updated_at": "2017-01-18T17:16:29Z",
+      "operating_system": {
+        "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+        "slug": "ubuntu_16_04",
+        "name": "Ubuntu 16.04 LTS",
+        "distro": "ubuntu",
+        "version": "16.04",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_2a",
+          "baremetal_3",
+          "baremetal_hua"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+        },
+        {
+          "href": "/ssh-keys/cf9db551-ec79-4c16-a776-97cdbcb3cec4"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "cbff502b-12ca-4986-b683-2cb4e119ae40",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "147.75.197.54",
+          "address": "147.75.197.55",
+          "gateway": "147.75.197.54",
+          "href": "/ips/cbff502b-12ca-4986-b683-2cb4e119ae40"
+        },
+        {
+          "id": "d5cec621-18c1-4e02-8f71-8dd46370c3e5",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/d5cec621-18c1-4e02-8f71-8dd46370c3e5"
+        },
+        {
+          "id": "6d206311-1f88-454d-9cf2-afca79f51fa3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/6d206311-1f88-454d-9cf2-afca79f51fa3"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": "soaz0#a=cd",
+      "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+    },
+    {
+      "id": "5bd27259-8b71-48f7-879d-c4e695cb2f33",
+      "short_id": "5bd27259",
+      "hostname": "andrea-device-livetest",
+      "description": null,
+      "state": "active",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.5bd27259",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-18T17:12:29Z",
+      "updated_at": "2017-01-18T17:16:29Z",
+      "operating_system": {
+        "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+        "slug": "ubuntu_16_04",
+        "name": "Ubuntu 16.04 LTS",
+        "distro": "ubuntu",
+        "version": "16.04",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_2a",
+          "baremetal_3",
+          "baremetal_hua"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+        },
+        {
+          "href": "/ssh-keys/cf9db551-ec79-4c16-a776-97cdbcb3cec4"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "cbff502b-12ca-4986-b683-2cb4e119ae40",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "147.75.197.54",
+          "address": "147.75.197.55",
+          "gateway": "147.75.197.54",
+          "href": "/ips/cbff502b-12ca-4986-b683-2cb4e119ae40"
+        },
+        {
+          "id": "d5cec621-18c1-4e02-8f71-8dd46370c3e5",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/d5cec621-18c1-4e02-8f71-8dd46370c3e5"
+        },
+        {
+          "id": "6d206311-1f88-454d-9cf2-afca79f51fa3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/6d206311-1f88-454d-9cf2-afca79f51fa3"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": "soaz0#a=cd",
+      "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+    },
+    {
+      "id": "5bd27259-8b71-48f7-879d-c4e695cb2f34",
+      "short_id": "5bd27259",
+      "hostname": "andrea-device-livetest",
+      "description": null,
+      "state": "active",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.5bd27259",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-18T17:12:29Z",
+      "updated_at": "2017-01-18T17:16:29Z",
+      "operating_system": {
+        "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+        "slug": "ubuntu_16_04",
+        "name": "Ubuntu 16.04 LTS",
+        "distro": "ubuntu",
+        "version": "16.04",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_2a",
+          "baremetal_3",
+          "baremetal_hua"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+        },
+        {
+          "href": "/ssh-keys/cf9db551-ec79-4c16-a776-97cdbcb3cec4"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "cbff502b-12ca-4986-b683-2cb4e119ae40",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "147.75.197.54",
+          "address": "147.75.197.55",
+          "gateway": "147.75.197.54",
+          "href": "/ips/cbff502b-12ca-4986-b683-2cb4e119ae40"
+        },
+        {
+          "id": "d5cec621-18c1-4e02-8f71-8dd46370c3e5",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/d5cec621-18c1-4e02-8f71-8dd46370c3e5"
+        },
+        {
+          "id": "6d206311-1f88-454d-9cf2-afca79f51fa3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/6d206311-1f88-454d-9cf2-afca79f51fa3"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": "soaz0#a=cd",
+      "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+    },
+    {
+      "id": "5bd27259-8b71-48f7-879d-c4e695cb2f35",
+      "short_id": "5bd27259",
+      "hostname": "andrea-device-livetest",
+      "description": null,
+      "state": "active",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.5bd27259",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-18T17:12:29Z",
+      "updated_at": "2017-01-18T17:16:29Z",
+      "operating_system": {
+        "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+        "slug": "ubuntu_16_04",
+        "name": "Ubuntu 16.04 LTS",
+        "distro": "ubuntu",
+        "version": "16.04",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_2a",
+          "baremetal_3",
+          "baremetal_hua"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+        },
+        {
+          "href": "/ssh-keys/cf9db551-ec79-4c16-a776-97cdbcb3cec4"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "cbff502b-12ca-4986-b683-2cb4e119ae40",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "147.75.197.54",
+          "address": "147.75.197.55",
+          "gateway": "147.75.197.54",
+          "href": "/ips/cbff502b-12ca-4986-b683-2cb4e119ae40"
+        },
+        {
+          "id": "d5cec621-18c1-4e02-8f71-8dd46370c3e5",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/d5cec621-18c1-4e02-8f71-8dd46370c3e5"
+        },
+        {
+          "id": "6d206311-1f88-454d-9cf2-afca79f51fa3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/6d206311-1f88-454d-9cf2-afca79f51fa3"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": "soaz0#a=cd",
+      "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=1"
+    },
+    "previous": null,
+    "self": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=1"
+    },
+    "next": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=2"
+    },
+    "last": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=2"
+    },
+    "total": 7
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/devices-last.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/devices-last.json b/packet/src/test/resources/devices-last.json
new file mode 100644
index 0000000..55ef2f0
--- /dev/null
+++ b/packet/src/test/resources/devices-last.json
@@ -0,0 +1,376 @@
+{
+  "devices": [
+    {
+      "id": "5bd27259-8b71-48f7-879d-c4e695cb2f36",
+      "short_id": "5bd27259",
+      "hostname": "andrea-device-livetest",
+      "description": null,
+      "state": "active",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.5bd27259",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-18T17:12:29Z",
+      "updated_at": "2017-01-18T17:16:29Z",
+      "operating_system": {
+        "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+        "slug": "ubuntu_16_04",
+        "name": "Ubuntu 16.04 LTS",
+        "distro": "ubuntu",
+        "version": "16.04",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_2a",
+          "baremetal_3",
+          "baremetal_hua"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+        },
+        {
+          "href": "/ssh-keys/cf9db551-ec79-4c16-a776-97cdbcb3cec4"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "cbff502b-12ca-4986-b683-2cb4e119ae40",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "147.75.197.54",
+          "address": "147.75.197.55",
+          "gateway": "147.75.197.54",
+          "href": "/ips/cbff502b-12ca-4986-b683-2cb4e119ae40"
+        },
+        {
+          "id": "d5cec621-18c1-4e02-8f71-8dd46370c3e5",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/d5cec621-18c1-4e02-8f71-8dd46370c3e5"
+        },
+        {
+          "id": "6d206311-1f88-454d-9cf2-afca79f51fa3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/6d206311-1f88-454d-9cf2-afca79f51fa3"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": "soaz0#a=cd",
+      "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+    },
+    {
+      "id": "5bd27259-8b71-48f7-879d-c4e695cb2f37",
+      "short_id": "5bd27259",
+      "hostname": "andrea-device-livetest",
+      "description": null,
+      "state": "active",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.5bd27259",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-18T17:12:29Z",
+      "updated_at": "2017-01-18T17:16:29Z",
+      "operating_system": {
+        "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+        "slug": "ubuntu_16_04",
+        "name": "Ubuntu 16.04 LTS",
+        "distro": "ubuntu",
+        "version": "16.04",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_2a",
+          "baremetal_3",
+          "baremetal_hua"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/f82b69d7-8c7e-4a38-9283-ecdbcaba56aa"
+        },
+        {
+          "href": "/ssh-keys/cf9db551-ec79-4c16-a776-97cdbcb3cec4"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "cbff502b-12ca-4986-b683-2cb4e119ae40",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "147.75.197.54",
+          "address": "147.75.197.55",
+          "gateway": "147.75.197.54",
+          "href": "/ips/cbff502b-12ca-4986-b683-2cb4e119ae40"
+        },
+        {
+          "id": "d5cec621-18c1-4e02-8f71-8dd46370c3e5",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/d5cec621-18c1-4e02-8f71-8dd46370c3e5"
+        },
+        {
+          "id": "6d206311-1f88-454d-9cf2-afca79f51fa3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-18T17:12:30Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "enabled": true,
+          "assigned_to": {
+            "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/6d206311-1f88-454d-9cf2-afca79f51fa3"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": "soaz0#a=cd",
+      "href": "/devices/5bd27259-8b71-48f7-879d-c4e695cb2f31"
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=1"
+    },
+    "previous": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=1"
+    },
+    "self": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=2"
+    },
+    "next": null,
+    "last": {
+      "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=2"
+    },
+    "total": 7
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/devices.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/devices.json b/packet/src/test/resources/devices.json
new file mode 100644
index 0000000..b9fbade
--- /dev/null
+++ b/packet/src/test/resources/devices.json
@@ -0,0 +1,282 @@
+{
+  "devices": [
+    {
+      "id": "98e22032-579e-4c04-bb12-05cc6a3864c8",
+      "short_id": "98e22032",
+      "hostname": "test",
+      "description": null,
+      "state": "provisioning",
+      "tags": [],
+      "billing_cycle": "hourly",
+      "user": "root",
+      "iqn": "iqn.2017-01.net.packet:device.98e22032",
+      "locked": false,
+      "bonding_mode": 5,
+      "created_at": "2017-01-03T09:47:59Z",
+      "updated_at": "2017-01-03T09:50:13Z",
+      "provisioning_percentage": 50,
+      "operating_system": {
+        "id": "68bad60d-f5a7-45c2-ad09-573edaad3a3c",
+        "slug": "centos_7",
+        "name": "Centos 7",
+        "distro": "centos",
+        "version": "7",
+        "provisionable_on": [
+          "baremetal_0",
+          "baremetal_1",
+          "baremetal_2",
+          "baremetal_3"
+        ]
+      },
+      "facility": {
+        "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+        "name": "Parsippany, NJ",
+        "code": "ewr1",
+        "features": [
+          "baremetal",
+          "storage"
+        ],
+        "address": null
+      },
+      "project": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "ssh_keys": [
+        {
+          "href": "/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"
+        },
+        {
+          "href": "/ssh-keys/a8d6cc17-7d9d-4fb9-8190-afdb301b67df"
+        },
+        {
+          "href": "/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"
+        },
+        {
+          "href": "/ssh-keys/eacfb002-45e1-4047-a0d5-cd9d8bab19ed"
+        },
+        {
+          "href": "/ssh-keys/bba63e41-b12c-493a-81d4-e52f50f247ed"
+        }
+      ],
+      "project_lite": {
+        "href": "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54"
+      },
+      "volumes": [],
+      "ip_addresses": [
+        {
+          "id": "5d0262c7-1727-411c-94c4-9e6f15490dd3",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-03T09:47:59Z",
+          "public": true,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "assigned_to": {
+            "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+          },
+          "network": "147.75.106.80",
+          "address": "147.75.106.81",
+          "gateway": "147.75.106.80",
+          "href": "/ips/5d0262c7-1727-411c-94c4-9e6f15490dd3"
+        },
+        {
+          "id": "f7d0e65c-eb3b-42bd-af9b-ad3a736d8d43",
+          "address_family": 6,
+          "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
+          "created_at": "2017-01-03T09:47:59Z",
+          "public": true,
+          "cidr": 127,
+          "management": true,
+          "manageable": true,
+          "assigned_to": {
+            "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+          },
+          "network": "2604:1380:2:9800::",
+          "address": "2604:1380:2:9800::1",
+          "gateway": "2604:1380:2:9800::",
+          "href": "/ips/f7d0e65c-eb3b-42bd-af9b-ad3a736d8d43"
+        },
+        {
+          "id": "a3d00b4e-d74f-4ac2-8bc9-91065d815b41",
+          "address_family": 4,
+          "netmask": "255.255.255.254",
+          "created_at": "2017-01-03T09:47:59Z",
+          "public": false,
+          "cidr": 31,
+          "management": true,
+          "manageable": true,
+          "assigned_to": {
+            "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+          },
+          "network": "10.99.214.0",
+          "address": "10.99.214.1",
+          "gateway": "10.99.214.0",
+          "href": "/ips/a3d00b4e-d74f-4ac2-8bc9-91065d815b41"
+        }
+      ],
+      "provisioning_events": [
+        {
+          "id": "bd62123b-afed-4e54-b1b9-89e219ba9cf0",
+          "type": "provisioning.101",
+          "body": "Provisioning started",
+          "created_at": "2017-01-03T09:47:59Z",
+          "relationships": [
+            {
+              "href": "#81909921-255e-413c-883a-c58d14c801ae"
+            }
+          ],
+          "interpolated": "Provisioning started",
+          "href": "/events/bd62123b-afed-4e54-b1b9-89e219ba9cf0"
+        },
+        {
+          "id": "14b4a9e6-be90-40ee-be48-b272f855e39c",
+          "type": "provisioning.102",
+          "body": "Network configured with addresses 147.75.106.81, 2604:1380:2:9800::1, and 10.99.214.1",
+          "created_at": "2017-01-03T09:48:46Z",
+          "relationships": [
+            {
+              "href": "#28588657-b8bf-44a1-98d3-27f8e04b660a"
+            }
+          ],
+          "interpolated": "Network configured with addresses 147.75.106.81, 2604:1380:2:9800::1, and 10.99.214.1",
+          "href": "/events/14b4a9e6-be90-40ee-be48-b272f855e39c"
+        },
+        {
+          "id": "57e3cadb-f9aa-4c73-be54-3c83e6cf462e",
+          "type": "provisioning.103",
+          "body": "Configuration written, restarting device",
+          "created_at": "2017-01-03T09:48:57Z",
+          "relationships": [
+            {
+              "href": "#25a7e807-f4aa-4f36-83cd-8347baeb26bc"
+            }
+          ],
+          "interpolated": "Configuration written, restarting device",
+          "href": "/events/57e3cadb-f9aa-4c73-be54-3c83e6cf462e"
+        },
+        {
+          "id": "b8322996-f57e-4c87-96cc-a16f33a0c305",
+          "type": "provisioning.104",
+          "body": "Connected to magic install system",
+          "created_at": "2017-01-03T09:50:13Z",
+          "relationships": [
+            {
+              "href": "#a4825d20-f7f2-426a-88db-38696bd3dfd6"
+            }
+          ],
+          "interpolated": "Connected to magic install system",
+          "href": "/events/b8322996-f57e-4c87-96cc-a16f33a0c305"
+        },
+        {
+          "id": "f00a00a9-7c59-420d-bdcf-2c0993303cf6",
+          "type": "provisioning.105",
+          "body": "Server partitions created",
+          "created_at": "2017-01-03T09:50:13Z",
+          "relationships": [
+            {
+              "href": "#6140659b-5e3d-4686-be8f-8879b6d3e27f"
+            }
+          ],
+          "interpolated": "Server partitions created",
+          "href": "/events/f00a00a9-7c59-420d-bdcf-2c0993303cf6"
+        },
+        {
+          "id": null,
+          "type": "provisioning.106",
+          "body": "Operating system packages installed",
+          "created_at": null,
+          "relationships": [],
+          "interpolated": "Operating system packages installed"
+        },
+        {
+          "id": null,
+          "type": "provisioning.107",
+          "body": "Server networking interfaces configured",
+          "created_at": null,
+          "relationships": [],
+          "interpolated": "Server networking interfaces configured"
+        },
+        {
+          "id": null,
+          "type": "provisioning.108",
+          "body": "Cloud-init packages installed and configured",
+          "created_at": null,
+          "relationships": [],
+          "interpolated": "Cloud-init packages installed and configured"
+        },
+        {
+          "id": null,
+          "type": "provisioning.109",
+          "body": "Installation finished, rebooting server",
+          "created_at": null,
+          "relationships": [],
+          "interpolated": "Installation finished, rebooting server"
+        },
+        {
+          "id": null,
+          "type": "provisioning.109",
+          "body": "Installation finished, rebooting server",
+          "created_at": null,
+          "relationships": [],
+          "interpolated": "Installation finished, rebooting server"
+        }
+      ],
+      "plan": {
+        "id": "e69c0169-4726-46ea-98f1-939c9e8a3607",
+        "slug": "baremetal_0",
+        "name": "Type 0",
+        "description": "Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.",
+        "line": "baremetal",
+        "specs": {
+          "cpus": [
+            {
+              "count": 1,
+              "type": "Intel Atom C2550 @ 2.4Ghz"
+            }
+          ],
+          "memory": {
+            "total": "8GB"
+          },
+          "drives": [
+            {
+              "count": 1,
+              "size": "80GB",
+              "type": "SSD"
+            }
+          ],
+          "nics": [
+            {
+              "count": 2,
+              "type": "1Gbps"
+            }
+          ],
+          "features": {
+            "raid": false,
+            "txt": true
+          }
+        },
+        "available_in": [
+          {
+            "href": "/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"
+          },
+          {
+            "href": "/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"
+          },
+          {
+            "href": "/facilities/8ea03255-89f9-4e62-9d3f-8817db82ceed"
+          },
+          {
+            "href": "/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"
+          }
+        ],
+        "pricing": {
+          "hour": 0.05
+        }
+      },
+      "userdata": "",
+      "root_password": ",q4*a8(eny",
+      "href": "/devices/98e22032-579e-4c04-bb12-05cc6a3864c8"
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/facilities-first.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/facilities-first.json b/packet/src/test/resources/facilities-first.json
new file mode 100644
index 0000000..5decf9a
--- /dev/null
+++ b/packet/src/test/resources/facilities-first.json
@@ -0,0 +1,39 @@
+{
+  "facilities": [
+    {
+      "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+      "name": "Parsippany, NJ",
+      "code": "ewr1",
+      "features": [
+        "baremetal",
+        "storage"
+      ],
+      "address": null
+    },
+    {
+      "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
+      "name": "Amsterdam, NL",
+      "code": "ams1",
+      "features": [
+        "storage"
+      ],
+      "address": null
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/facilities?page=1"
+    },
+    "previous": null,
+    "self": {
+      "href": "/facilities?page=1"
+    },
+    "next": {
+      "href": "/facilities?page=2"
+    },
+    "last": {
+      "href": "/facilities?page=2"
+    },
+    "total": 3
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/facilities-last.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/facilities-last.json b/packet/src/test/resources/facilities-last.json
new file mode 100644
index 0000000..f5e6d1c
--- /dev/null
+++ b/packet/src/test/resources/facilities-last.json
@@ -0,0 +1,27 @@
+{
+  "facilities": [
+    {
+      "id": "2b70eb8f-fa18-47c0-aba7-222a842362fd",
+      "name": "Sunnyvale, CA",
+      "code": "sjc1",
+      "features": [],
+      "address": null
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/facilities?page=1"
+    },
+    "previous": {
+      "href": "/facilities?page=1"
+    },
+    "self": {
+      "href": "/facilities?page=2"
+    },
+    "next": null,
+    "last": {
+      "href": "/facilities?page=2"
+    },
+    "total": 3
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/facilities.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/facilities.json b/packet/src/test/resources/facilities.json
new file mode 100644
index 0000000..ed6ab4f
--- /dev/null
+++ b/packet/src/test/resources/facilities.json
@@ -0,0 +1,30 @@
+{
+  "facilities": [
+    {
+      "id": "e1e9c52e-a0bc-4117-b996-0fc94843ea09",
+      "name": "Parsippany, NJ",
+      "code": "ewr1",
+      "features": [
+        "baremetal",
+        "storage"
+      ],
+      "address": null
+    },
+    {
+      "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
+      "name": "Amsterdam, NL",
+      "code": "ams1",
+      "features": [
+        "storage"
+      ],
+      "address": null
+    },
+    {
+      "id": "2b70eb8f-fa18-47c0-aba7-222a842362fd",
+      "name": "Sunnyvale, CA",
+      "code": "sjc1",
+      "features": [],
+      "address": null
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/operatingSystems-first.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/operatingSystems-first.json b/packet/src/test/resources/operatingSystems-first.json
new file mode 100644
index 0000000..1438108
--- /dev/null
+++ b/packet/src/test/resources/operatingSystems-first.json
@@ -0,0 +1,96 @@
+{
+  "operating_systems": [
+    {
+      "id": "06e21644-a769-11e6-80f5-76304dec7eb7",
+      "slug": "alpine_3",
+      "name": "Alpine 3",
+      "distro": "alpine",
+      "version": "3",
+      "provisionable_on": []
+    },
+    {
+      "id": "06e21978-a769-11e6-80f5-76304dec7eb7",
+      "slug": "centos_6",
+      "name": "CentOS 6",
+      "distro": "centos",
+      "version": "6",
+      "provisionable_on": []
+    },
+    {
+      "id": "68bad60d-f5a7-45c2-ad09-573edaad3a3c",
+      "slug": "centos_7",
+      "name": "Centos 7",
+      "distro": "centos",
+      "version": "7",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e21ce8-a769-11e6-80f5-76304dec7eb7",
+      "slug": "coreos_alpha",
+      "name": "CoreOS Alpha",
+      "distro": "coreos",
+      "version": "alpha",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "6345f310-0bb0-4b59-b051-954fed05183a",
+      "slug": "coreos_beta",
+      "name": "CoreOS Beta",
+      "distro": "coreos",
+      "version": "beta",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "d61c3912-8422-4daf-835e-854efa0062e4",
+      "slug": "coreos_stable",
+      "name": "CoreOS Stable",
+      "distro": "coreos",
+      "version": "stable",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e21e78-a769-11e6-80f5-76304dec7eb7",
+      "slug": "debian_7",
+      "name": "Debian 7",
+      "distro": "debian",
+      "version": "7",
+      "provisionable_on": []
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/operating-systems?page=1"
+    },
+    "previous": null,
+    "self": {
+      "href": "/operating-systems?page=1"
+    },
+    "next": {
+      "href": "/operating-systems?page=2"
+    },
+    "last": {
+      "href": "/operating-systems?page=2"
+    },
+    "total": 14
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/resources/operatingSystems-last.json
----------------------------------------------------------------------
diff --git a/packet/src/test/resources/operatingSystems-last.json b/packet/src/test/resources/operatingSystems-last.json
new file mode 100644
index 0000000..2c3c8e3
--- /dev/null
+++ b/packet/src/test/resources/operatingSystems-last.json
@@ -0,0 +1,106 @@
+{
+  "operating_systems": [
+    {
+      "id": "88239019-abc7-41e8-9a4d-cd334da97ff1",
+      "slug": "debian_8",
+      "name": "Debian 8",
+      "distro": "debian",
+      "version": "8",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e21ffe-a769-11e6-80f5-76304dec7eb7",
+      "slug": "deprovision",
+      "name": "Deprovision",
+      "distro": "centos",
+      "version": "",
+      "provisionable_on": []
+    },
+    {
+      "id": "06e22198-a769-11e6-80f5-76304dec7eb7",
+      "slug": "freebsd_10_3",
+      "name": "FreeBSD 10.3",
+      "distro": "freebsd",
+      "version": "10.3",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e22328-a769-11e6-80f5-76304dec7eb7",
+      "slug": "rancher",
+      "name": "RancherOS",
+      "distro": "rancher",
+      "version": "latest",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e224b8-a769-11e6-80f5-76304dec7eb7",
+      "slug": "ubuntu_14_04",
+      "name": "Ubuntu 14.04 LTS",
+      "distro": "ubuntu",
+      "version": "14.04",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "1b9b78e3-de68-466e-ba00-f2123e89c112",
+      "slug": "ubuntu_16_04",
+      "name": "Ubuntu 16.04 LTS",
+      "distro": "ubuntu",
+      "version": "16.04",
+      "provisionable_on": [
+        "baremetal_0",
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_2a",
+        "baremetal_3"
+      ]
+    },
+    {
+      "id": "06e22972-a769-11e6-80f5-76304dec7eb7",
+      "slug": "windows_2012_rc2",
+      "name": "Windows 2012 RC2",
+      "distro": "windows",
+      "version": "2012 RC2",
+      "provisionable_on": [
+        "baremetal_1",
+        "baremetal_2",
+        "baremetal_3"
+      ]
+    }
+  ],
+  "meta": {
+    "first": {
+      "href": "/operating-systems?page=1"
+    },
+    "previous": {
+      "href": "/operating-systems?page=1"
+    },
+    "self": {
+      "href": "/operating-systems?page=2"
+    },
+    "next": null,
+    "last": {
+      "href": "/operating-systems?page=2"
+    },
+    "total": 14
+  }
+}


[3/3] jclouds-labs git commit: Add remaining features to support the abstraction

Posted by an...@apache.org.
Add remaining features to support the abstraction

- add DeviceApi with Mock and Live Test
- add FacilityApi with Mock and Live Test
- add OperatingSystemApi with Mock and Live Test
- add SshKeyApi with Mock and Live Test
- fix Device domain object
- refactor deviceApi.create and sshKeyApi.create as they actually return an object instead of a URI now
- add mock and live tests for device api actions


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs/commit/f749c88e
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs/tree/f749c88e
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs/diff/f749c88e

Branch: refs/heads/master
Commit: f749c88ec6b761a9c03cd654869d8500cf669884
Parents: dfd6804
Author: Andrea Turli <an...@gmail.com>
Authored: Wed Jan 18 19:13:28 2017 +0100
Committer: Andrea Turli <an...@gmail.com>
Committed: Tue Jan 24 10:36:57 2017 +0100

----------------------------------------------------------------------
 .../main/java/org/jclouds/packet/PacketApi.java |  46 +
 .../packet/config/PacketHttpApiModule.java      |   4 +-
 .../java/org/jclouds/packet/domain/Device.java  |  82 +-
 .../packet/domain/ProvisioningEvent.java        |  52 ++
 .../org/jclouds/packet/features/DeviceApi.java  | 148 +++
 .../jclouds/packet/features/FacilityApi.java    |  94 ++
 .../packet/features/OperatingSystemApi.java     |  94 ++
 .../org/jclouds/packet/features/PlanApi.java    |  94 ++
 .../org/jclouds/packet/features/ProjectApi.java |   6 +-
 .../org/jclouds/packet/features/SshKeyApi.java  | 122 +++
 .../packet/functions/BaseToPagedIterable.java   |   8 +-
 .../packet/functions/HrefToListOptions.java     |  63 ++
 .../packet/functions/LinkToListOptions.java     |  63 --
 .../packet/features/DeviceApiLiveTest.java      | 138 +++
 .../packet/features/DeviceApiMockTest.java      | 184 ++++
 .../packet/features/FacilityApiLiveTest.java    |  62 ++
 .../packet/features/FacilityApiMockTest.java    |  78 ++
 .../features/OperatingSystemApiLiveTest.java    |  62 ++
 .../features/OperatingSystemApiMockTest.java    |  78 ++
 .../packet/features/PlanApiLiveTest.java        |  62 ++
 .../packet/features/PlanApiMockTest.java        |  78 ++
 .../packet/features/ProjectApiLiveTest.java     |   7 +-
 .../packet/features/ProjectApiMockTest.java     |   1 -
 .../packet/features/SshKeyApiLiveTest.java      |  83 ++
 .../packet/features/SshKeyApiMockTest.java      | 134 +++
 .../packet/functions/HrefToListOptionsTest.java |  57 ++
 .../packet/functions/LinkToListOptionsTest.java |  57 --
 .../src/test/resources/device-create-req.json   |  11 +
 .../src/test/resources/device-create-res.json   | 211 +++++
 packet/src/test/resources/device.json           | 278 ++++++
 packet/src/test/resources/devices-first.json    | 910 +++++++++++++++++++
 packet/src/test/resources/devices-last.json     | 376 ++++++++
 packet/src/test/resources/devices.json          | 282 ++++++
 packet/src/test/resources/facilities-first.json |  39 +
 packet/src/test/resources/facilities-last.json  |  27 +
 packet/src/test/resources/facilities.json       |  30 +
 .../test/resources/operatingSystems-first.json  |  96 ++
 .../test/resources/operatingSystems-last.json   | 106 +++
 packet/src/test/resources/operatingSystems.json | 166 ++++
 packet/src/test/resources/plans-first.json      | 222 +++++
 packet/src/test/resources/plans-last.json       |  98 ++
 packet/src/test/resources/plans.json            | 284 ++++++
 packet/src/test/resources/power-off.json        |   3 +
 packet/src/test/resources/power-on.json         |   3 +
 packet/src/test/resources/projects.json         |  78 +-
 packet/src/test/resources/reboot.json           |   3 +
 packet/src/test/resources/rescue.json           |   3 +
 .../src/test/resources/ssh-key-create-req.json  |   4 +
 .../src/test/resources/ssh-key-create-res.json  |  12 +
 packet/src/test/resources/ssh-key.json          |  12 +
 packet/src/test/resources/sshKeys-first.json    |  80 ++
 packet/src/test/resources/sshKeys-last.json     |  56 ++
 packet/src/test/resources/sshKeys.json          |  16 +
 packet/src/test/resources/user.json             |  19 +
 54 files changed, 5244 insertions(+), 138 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/PacketApi.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/PacketApi.java b/packet/src/main/java/org/jclouds/packet/PacketApi.java
index 1cb8e9b..e5f84e3 100644
--- a/packet/src/main/java/org/jclouds/packet/PacketApi.java
+++ b/packet/src/main/java/org/jclouds/packet/PacketApi.java
@@ -18,7 +18,14 @@ package org.jclouds.packet;
 
 import java.io.Closeable;
 
+import javax.ws.rs.PathParam;
+
+import org.jclouds.packet.features.DeviceApi;
+import org.jclouds.packet.features.FacilityApi;
+import org.jclouds.packet.features.OperatingSystemApi;
+import org.jclouds.packet.features.PlanApi;
 import org.jclouds.packet.features.ProjectApi;
+import org.jclouds.packet.features.SshKeyApi;
 import org.jclouds.rest.annotations.Delegate;
 
 /**
@@ -37,4 +44,43 @@ public interface PacketApi extends Closeable {
    @Delegate
    ProjectApi projectApi();
 
+   /**
+    * This Packet API provides all of the devices
+    *
+    * @see <a href="https://www.packet.net/help/api/#page:devices">docs</a>
+    */
+   @Delegate
+   DeviceApi deviceApi(@PathParam("projectId") String projectId);
+
+   /**
+    * This Packet API provides all of the facilities
+    *
+    * @see <a href="https://www.packet.net/help/api/#page:devices,header:devices-operating-systems">docs</a>
+    */
+   @Delegate
+   FacilityApi facilityApi();
+
+   /**
+    * This Packet API provides all of the plans
+    *
+    * @see <a href="https://www.packet.net/help/api/#page:devices,header:devices-plans">docs</a>
+    */
+   @Delegate
+   PlanApi planApi();
+
+   /**
+    * This Packet API provides all of the operating systems
+    *
+    * @see <a href="https://www.packet.net/help/api/#page:devices,header:devices-operating-systems">docs</a>
+    */
+   @Delegate
+   OperatingSystemApi operatingSystemApi();
+
+   /**
+    * This Packet API provides all of the operating systems
+    *
+    * @see <a href="https://www.packet.net/help/api/#page:ssh-keys">docs</a>
+    */
+   @Delegate
+   SshKeyApi sshKeyApi();
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java b/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java
index e74bb19..6edeebd 100644
--- a/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java
+++ b/packet/src/main/java/org/jclouds/packet/config/PacketHttpApiModule.java
@@ -25,7 +25,7 @@ import org.jclouds.location.suppliers.implicit.FirstRegion;
 import org.jclouds.packet.PacketApi;
 import org.jclouds.packet.domain.Href;
 import org.jclouds.packet.domain.options.ListOptions;
-import org.jclouds.packet.functions.LinkToListOptions;
+import org.jclouds.packet.functions.HrefToListOptions;
 import org.jclouds.packet.handlers.PacketErrorHandler;
 import org.jclouds.rest.ConfiguresHttpApi;
 import org.jclouds.rest.config.HttpApiModule;
@@ -42,7 +42,7 @@ public class PacketHttpApiModule extends HttpApiModule<PacketApi> {
       super.configure();
       bind(ImplicitLocationSupplier.class).to(FirstRegion.class).in(Scopes.SINGLETON);
       bind(new TypeLiteral<Function<Href, ListOptions>>() {
-      }).to(LinkToListOptions.class);
+      }).to(HrefToListOptions.class);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/domain/Device.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/domain/Device.java b/packet/src/main/java/org/jclouds/packet/domain/Device.java
index af59970..96e0b53 100644
--- a/packet/src/main/java/org/jclouds/packet/domain/Device.java
+++ b/packet/src/main/java/org/jclouds/packet/domain/Device.java
@@ -18,6 +18,8 @@ package org.jclouds.packet.domain;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.json.SerializedNames;
@@ -27,6 +29,8 @@ import com.google.common.base.Enums;
 import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
@@ -60,15 +64,19 @@ public abstract class Device {
     public abstract OperatingSystem operatingSystem();
     public abstract Facility facility();
     public abstract Href project();
+    public abstract List<Href> sshKeys();
     public abstract Href projectLite();
     public abstract List<Object> volumes();
     public abstract List<IpAddress> ipAddresses();
+    public abstract List<ProvisioningEvent> provisioningEvents();
     public abstract Plan plan();
     public abstract String rootPassword();
     public abstract String userdata();
     public abstract String href();
 
-    @SerializedNames({"id", "short_id", "hostname", "description", "state", "tags", "billing_cycle", "user", "iqn", "locked", "bonding_mode", "created_at", "updated_at", "operating_system", "facility", "project", "project_lite", "volumes", "ip_addresses", "plan", "root_password", "userdata", "href"})
+    @SerializedNames({"id", "short_id", "hostname", "description", "state", "tags", "billing_cycle", "user", "iqn",
+            "locked", "bonding_mode", "created_at", "updated_at", "operating_system", "facility", "project", "ssh_keys",
+            "project_lite", "volumes", "ip_addresses", "provisioning_events", "plan", "root_password", "userdata", "href"})
     public static Device create(String id,
                                 String shortId,
                                 String hostname,
@@ -85,9 +93,11 @@ public abstract class Device {
                                 OperatingSystem operatingSystem,
                                 Facility facility,
                                 Href project,
+                                List<Href> sshKeys,
                                 Href projectLite,
                                 List<Object> volumes,
                                 List<IpAddress> ipAddresses,
+                                List<ProvisioningEvent> provisioningEvents,
                                 Plan plan,
                                 String rootPassword,
                                 String userdata,
@@ -95,14 +105,80 @@ public abstract class Device {
     ) {
         return new AutoValue_Device(id, shortId, hostname, description, state,
                 tags == null ? ImmutableList.<String> of() : ImmutableList.copyOf(tags),
-                billingCycle, user, iqn, locked, bondingMode, createdAt, updatedAt, operatingSystem, facility, project, projectLite,
+                billingCycle, user, iqn, locked, bondingMode, createdAt, updatedAt, operatingSystem, facility, project,
+                sshKeys == null ? ImmutableList.<Href> of() : ImmutableList.copyOf(sshKeys),
+                projectLite,
                 volumes == null ? ImmutableList.of() : ImmutableList.copyOf(volumes),
                 ipAddresses == null ? ImmutableList.<IpAddress>of() : ImmutableList.copyOf(ipAddresses),
-                plan, rootPassword, userdata, href
+                provisioningEvents == null ? ImmutableList.<ProvisioningEvent> of() : ImmutableList.copyOf(provisioningEvents),
+                plan,
+                rootPassword, userdata, href
         );
     }
 
     Device() {
     }
 
+    @AutoValue
+    public abstract static class CreateDevice {
+
+        public abstract String hostname();
+        public abstract String plan();
+        public abstract String billingCycle();
+        public abstract String facility();
+        public abstract Map<String, String> features();
+        public abstract String operatingSystem();
+        public abstract Boolean locked();
+        public abstract String userdata();
+        public abstract Set<String> tags();
+
+        @SerializedNames({"hostname", "plan", "billing_cycle", "facility", "features", "operating_system",
+                "locked", "userdata", "tags" })
+        private static CreateDevice create(final String hostname, final String plan, final String billingCycle,
+                                          final String facility, final Map<String, String> features, final String operatingSystem,
+                                          final Boolean locked, final String userdata,
+                                          final Set<String> tags) {
+            return builder()
+                    .hostname(hostname)
+                    .plan(plan)
+                    .billingCycle(billingCycle)
+                    .facility(facility)
+                    .features(features)
+                    .operatingSystem(operatingSystem)
+                    .locked(locked)
+                    .userdata(userdata)
+                    .tags(tags)
+                    .build();
+        }
+
+        public static Builder builder() {
+            return new AutoValue_Device_CreateDevice.Builder();
+        }
+
+        @AutoValue.Builder
+        public abstract static class Builder {
+
+            public abstract Builder hostname(String hostname);
+            public abstract Builder plan(String plan);
+            public abstract Builder billingCycle(String billingCycle);
+            public abstract Builder facility(String facility);
+            public abstract Builder features(Map<String, String> features);
+            public abstract Builder operatingSystem(String operatingSystem);
+            public abstract Builder locked(Boolean locked);
+            public abstract Builder userdata(String userdata);
+            public abstract Builder tags(Set<String> tags);
+
+           abstract Map<String, String> features();
+           abstract Set<String> tags();
+
+           abstract CreateDevice autoBuild();
+
+           public CreateDevice build() {
+              return tags(tags() != null ? ImmutableSet.copyOf(tags()) : ImmutableSet.<String> of())
+                      .features(features() != null ? ImmutableMap.copyOf(features()) : ImmutableMap.<String, String> of())
+                      .autoBuild();
+           }
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/domain/ProvisioningEvent.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/domain/ProvisioningEvent.java b/packet/src/main/java/org/jclouds/packet/domain/ProvisioningEvent.java
new file mode 100644
index 0000000..3df979f
--- /dev/null
+++ b/packet/src/main/java/org/jclouds/packet/domain/ProvisioningEvent.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.domain;
+
+import java.util.Date;
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class ProvisioningEvent {
+
+   @Nullable
+   public abstract String id();
+   public abstract String type();
+   public abstract String body();
+   @Nullable
+   public abstract Date createdAt();
+   public abstract List<Href> relationships();
+   public abstract String interpolated();
+   @Nullable
+   public abstract String href();
+
+   @SerializedNames({"id", "type", "body", "created_at", "relationships", "interpolated", "href"})
+   public static ProvisioningEvent create(String id, String type, String body, Date createdAt,
+                                          List<Href> relationships, String interpolated, String href) {
+      return new AutoValue_ProvisioningEvent(id, type, body, createdAt,
+              relationships == null ? ImmutableList.<Href> of() : ImmutableList.copyOf(relationships),
+              interpolated,
+              href);
+   }
+
+   ProvisioningEvent() {}
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java b/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java
new file mode 100644
index 0000000..9f4c672
--- /dev/null
+++ b/packet/src/main/java/org/jclouds/packet/features/DeviceApi.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.collect.internal.Arg0ToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.domain.ActionType;
+import org.jclouds.packet.domain.Device;
+import org.jclouds.packet.domain.Href;
+import org.jclouds.packet.domain.internal.PaginatedCollection;
+import org.jclouds.packet.domain.options.ListOptions;
+import org.jclouds.packet.filters.AddApiVersionToRequest;
+import org.jclouds.packet.filters.AddXAuthTokenToRequest;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+@Path("/projects/{projectId}/devices")
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestFilters({AddXAuthTokenToRequest.class, AddApiVersionToRequest.class})
+public interface DeviceApi {
+
+   @Named("device:list")
+   @GET
+   @ResponseParser(ParseDevices.class)
+   @Transform(ParseDevices.ToPagedIterable.class)
+   @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+   PagedIterable<Device> list();
+
+   @Named("device:list")
+   @GET
+   @ResponseParser(ParseDevices.class)
+   @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+   IterableWithMarker<Device> list(ListOptions options);
+
+   final class ParseDevices extends ParseJson<ParseDevices.Devices> {
+      @Inject
+      ParseDevices(Json json) {
+         super(json, TypeLiteral.get(Devices.class));
+      }
+
+       private static class Devices extends PaginatedCollection<Device> {
+         @ConstructorProperties({"devices", "meta"})
+         public Devices(List<Device> items, Meta meta) {
+            super(items, meta);
+         }
+      }
+
+      public static class ToPagedIterable extends Arg0ToPagedIterable.FromCaller<Device, ToPagedIterable> {
+
+         private final PacketApi api;
+         private final Function<Href, ListOptions> hrefToOptions;
+
+         @Inject
+         ToPagedIterable(PacketApi api, Function<Href, ListOptions> hrefToOptions) {
+            this.api = api;
+            this.hrefToOptions = hrefToOptions;
+         }
+
+         @Override
+         protected Function<Object, IterableWithMarker<Device>> markerToNextForArg0(Optional<Object> arg0) {
+            String projectId = arg0.get().toString();
+            final DeviceApi deviceApi = api.deviceApi(projectId);
+            return new Function<Object, IterableWithMarker<Device>>() {
+
+               @SuppressWarnings("unchecked")
+               @Override
+               public IterableWithMarker<Device> apply(Object input) {
+                  ListOptions listOptions = hrefToOptions.apply(Href.class.cast(input));
+                  return IterableWithMarker.class.cast(deviceApi.list(listOptions));
+               }
+
+            };
+         }
+      }
+   }
+
+   @Named("device:create")
+   @POST
+   @Produces(MediaType.APPLICATION_JSON)
+   Device create(@BinderParam(BindToJsonPayload.class) Device.CreateDevice device);
+
+
+   @Named("device:get")
+   @GET
+   @Path("/{id}")
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   Device get(@PathParam("id") String id);
+
+   @Named("device:delete")
+   @DELETE
+   @Path("/{id}")
+   @Fallback(VoidOnNotFoundOr404.class)
+   void delete(@PathParam("id") String id);
+
+   @Named("device:actions")
+   @POST
+   @Path("/{id}/actions")
+   @MapBinder(BindToJsonPayload.class)
+   void actions(@PathParam("id") String id, @PayloadParam("type") ActionType type);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/features/FacilityApi.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/features/FacilityApi.java b/packet/src/main/java/org/jclouds/packet/features/FacilityApi.java
new file mode 100644
index 0000000..bde9898
--- /dev/null
+++ b/packet/src/main/java/org/jclouds/packet/features/FacilityApi.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.domain.Facility;
+import org.jclouds.packet.domain.Href;
+import org.jclouds.packet.domain.internal.PaginatedCollection;
+import org.jclouds.packet.domain.options.ListOptions;
+import org.jclouds.packet.filters.AddApiVersionToRequest;
+import org.jclouds.packet.filters.AddXAuthTokenToRequest;
+import org.jclouds.packet.functions.BaseToPagedIterable;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+@Path("/facilities")
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestFilters({ AddXAuthTokenToRequest.class, AddApiVersionToRequest.class} )
+public interface FacilityApi {
+
+    @Named("facility:list")
+    @GET
+    @ResponseParser(ParseFacilities.class)
+    @Transform(ParseFacilities.ToPagedIterable.class)
+    @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+    PagedIterable<Facility> list();
+
+    @Named("facility:list")
+    @GET
+    @ResponseParser(ParseFacilities.class)
+    @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+    IterableWithMarker<Facility> list(ListOptions options);
+
+    final class ParseFacilities extends ParseJson<ParseFacilities.Facilities> {
+        @Inject
+        ParseFacilities(Json json) {
+            super(json, TypeLiteral.get(Facilities.class));
+        }
+
+        private static class Facilities extends PaginatedCollection<Facility> {
+            @ConstructorProperties({ "facilities", "meta" })
+            public Facilities(List<Facility> items, Meta meta) {
+                super(items, meta);
+            }
+        }
+
+        private static class ToPagedIterable extends BaseToPagedIterable<Facility, ListOptions> {
+            @Inject ToPagedIterable(PacketApi api, Function<Href, ListOptions> hrefToOptions) {
+                super(api, hrefToOptions);
+            }
+
+            @Override
+            protected IterableWithMarker<Facility> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+                return api.facilityApi().list(options);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/features/OperatingSystemApi.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/features/OperatingSystemApi.java b/packet/src/main/java/org/jclouds/packet/features/OperatingSystemApi.java
new file mode 100644
index 0000000..401b1e9
--- /dev/null
+++ b/packet/src/main/java/org/jclouds/packet/features/OperatingSystemApi.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.domain.Href;
+import org.jclouds.packet.domain.OperatingSystem;
+import org.jclouds.packet.domain.internal.PaginatedCollection;
+import org.jclouds.packet.domain.options.ListOptions;
+import org.jclouds.packet.filters.AddApiVersionToRequest;
+import org.jclouds.packet.filters.AddXAuthTokenToRequest;
+import org.jclouds.packet.functions.BaseToPagedIterable;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+@Path("/operating-systems")
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestFilters({ AddXAuthTokenToRequest.class, AddApiVersionToRequest.class} )
+public interface OperatingSystemApi {
+
+    @Named("operatingsystem:list")
+    @GET
+    @ResponseParser(ParseOperatingSystems.class)
+    @Transform(ParseOperatingSystems.ToPagedIterable.class)
+    @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+    PagedIterable<OperatingSystem> list();
+
+    @Named("operatingsystem:list")
+    @GET
+    @ResponseParser(ParseOperatingSystems.class)
+    @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+    IterableWithMarker<OperatingSystem> list(ListOptions options);
+
+    final class ParseOperatingSystems extends ParseJson<ParseOperatingSystems.OperatingSystems> {
+        @Inject
+        ParseOperatingSystems(Json json) {
+            super(json, TypeLiteral.get(ParseOperatingSystems.OperatingSystems.class));
+        }
+
+        private static class OperatingSystems extends PaginatedCollection<OperatingSystem> {
+            @ConstructorProperties({ "operating_systems", "meta" })
+            public OperatingSystems(List<OperatingSystem> items, Meta meta) {
+                super(items, meta);
+            }
+        }
+
+        private static class ToPagedIterable extends BaseToPagedIterable<OperatingSystem, ListOptions> {
+            @Inject ToPagedIterable(PacketApi api, Function<Href, ListOptions> hrefToOptions) {
+                super(api, hrefToOptions);
+            }
+
+            @Override
+            protected IterableWithMarker<OperatingSystem> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+                return api.operatingSystemApi().list(options);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/features/PlanApi.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/features/PlanApi.java b/packet/src/main/java/org/jclouds/packet/features/PlanApi.java
new file mode 100644
index 0000000..7ed5c3a
--- /dev/null
+++ b/packet/src/main/java/org/jclouds/packet/features/PlanApi.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.domain.Href;
+import org.jclouds.packet.domain.Plan;
+import org.jclouds.packet.domain.internal.PaginatedCollection;
+import org.jclouds.packet.domain.options.ListOptions;
+import org.jclouds.packet.filters.AddApiVersionToRequest;
+import org.jclouds.packet.filters.AddXAuthTokenToRequest;
+import org.jclouds.packet.functions.BaseToPagedIterable;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+@Path("/plans")
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestFilters({ AddXAuthTokenToRequest.class, AddApiVersionToRequest.class} )
+public interface PlanApi {
+
+    @Named("plan:list")
+    @GET
+    @ResponseParser(ParsePlans.class)
+    @Transform(ParsePlans.ToPagedIterable.class)
+    @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+    PagedIterable<Plan> list();
+
+    @Named("plan:list")
+    @GET
+    @ResponseParser(ParsePlans.class)
+    @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+    IterableWithMarker<Plan> list(ListOptions options);
+
+    final class ParsePlans extends ParseJson<ParsePlans.Plans> {
+        @Inject
+        ParsePlans(Json json) {
+            super(json, TypeLiteral.get(ParsePlans.Plans.class));
+        }
+
+        private static class Plans extends PaginatedCollection<Plan> {
+            @ConstructorProperties({ "plans", "meta" })
+            public Plans(List<Plan> items, Meta meta) {
+                super(items, meta);
+            }
+        }
+
+        private static class ToPagedIterable extends BaseToPagedIterable<Plan, ListOptions> {
+            @Inject ToPagedIterable(PacketApi api, Function<Href, ListOptions> hrefToOptions) {
+                super(api, hrefToOptions);
+            }
+
+            @Override
+            protected IterableWithMarker<Plan> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+                return api.planApi().list(options);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java b/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java
index afdf1ef..9da50d0 100644
--- a/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java
+++ b/packet/src/main/java/org/jclouds/packet/features/ProjectApi.java
@@ -53,7 +53,6 @@ import com.google.inject.TypeLiteral;
 @RequestFilters({ AddXAuthTokenToRequest.class, AddApiVersionToRequest.class} )
 public interface ProjectApi {
 
-
     @Named("project:list")
     @GET
     @ResponseParser(ParseProjects.class)
@@ -81,8 +80,8 @@ public interface ProjectApi {
         }
 
         private static class ToPagedIterable extends BaseToPagedIterable<Project, ListOptions> {
-            @Inject ToPagedIterable(PacketApi api, Function<Href, ListOptions> linkToOptions) {
-                super(api, linkToOptions);
+            @Inject ToPagedIterable(PacketApi api, Function<Href, ListOptions> hrefToOptions) {
+                super(api, hrefToOptions);
             }
 
             @Override
@@ -91,4 +90,5 @@ public interface ProjectApi {
             }
         }
     }
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/features/SshKeyApi.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/features/SshKeyApi.java b/packet/src/main/java/org/jclouds/packet/features/SshKeyApi.java
new file mode 100644
index 0000000..cd22107
--- /dev/null
+++ b/packet/src/main/java/org/jclouds/packet/features/SshKeyApi.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.Json;
+import org.jclouds.packet.PacketApi;
+import org.jclouds.packet.domain.Href;
+import org.jclouds.packet.domain.SshKey;
+import org.jclouds.packet.domain.internal.PaginatedCollection;
+import org.jclouds.packet.domain.options.ListOptions;
+import org.jclouds.packet.filters.AddApiVersionToRequest;
+import org.jclouds.packet.filters.AddXAuthTokenToRequest;
+import org.jclouds.packet.functions.BaseToPagedIterable;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.Transform;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+@Path("/ssh-keys")
+@Consumes(MediaType.APPLICATION_JSON)
+@RequestFilters({ AddXAuthTokenToRequest.class, AddApiVersionToRequest.class} )
+public interface SshKeyApi {
+
+    @Named("sshkey:list")
+    @GET
+    @ResponseParser(ParseSshKeys.class)
+    @Transform(ParseSshKeys.ToPagedIterable.class)
+    @Fallback(Fallbacks.EmptyPagedIterableOnNotFoundOr404.class)
+    PagedIterable<SshKey> list();
+
+    @Named("sshkey:list")
+    @GET
+    @ResponseParser(ParseSshKeys.class)
+    @Fallback(Fallbacks.EmptyIterableWithMarkerOnNotFoundOr404.class)
+    IterableWithMarker<SshKey> list(ListOptions options);
+
+    final class ParseSshKeys extends ParseJson<ParseSshKeys.SshKeys> {
+        @Inject
+        ParseSshKeys(Json json) {
+            super(json, TypeLiteral.get(ParseSshKeys.SshKeys.class));
+        }
+
+        private static class SshKeys extends PaginatedCollection<SshKey> {
+            @ConstructorProperties({ "ssh_keys", "meta" })
+            public SshKeys(List<SshKey> items, Meta meta) {
+                super(items, meta);
+            }
+        }
+
+        private static class ToPagedIterable extends BaseToPagedIterable<SshKey, ListOptions> {
+            @Inject ToPagedIterable(PacketApi api, Function<Href, ListOptions> hrefToOptions) {
+                super(api, hrefToOptions);
+            }
+
+            @Override
+            protected IterableWithMarker<SshKey> fetchPageUsingOptions(ListOptions options, Optional<Object> arg0) {
+                return api.sshKeyApi().list(options);
+            }
+        }
+    }
+
+    @Named("sshkey:create")
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @MapBinder(BindToJsonPayload.class)
+    SshKey create(@PayloadParam("label") String label, @PayloadParam("key") String key);
+
+    @Named("sshkey:get")
+    @GET
+    @Path("/{id}")
+    @Fallback(NullOnNotFoundOr404.class)
+    @Nullable
+    SshKey get(@PathParam("id") String id);
+
+    @Named("sshkey:delete")
+    @DELETE
+    @Path("/{id}")
+    @Fallback(VoidOnNotFoundOr404.class)
+    void delete(@PathParam("id") String id);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java b/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java
index c5c275b..abc59a2 100644
--- a/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java
+++ b/packet/src/main/java/org/jclouds/packet/functions/BaseToPagedIterable.java
@@ -35,12 +35,12 @@ import com.google.common.base.Optional;
  */
 public abstract class BaseToPagedIterable<T, O extends ListOptions> extends
         Arg0ToPagedIterable<T, BaseToPagedIterable<T, O>> {
-   private final Function<Href, O> linkToOptions;
+   private final Function<Href, O> hrefToOptions;
    protected final PacketApi api;
 
-   @Inject protected BaseToPagedIterable(PacketApi api, Function<Href, O> linkToOptions) {
+   @Inject protected BaseToPagedIterable(PacketApi api, Function<Href, O> hrefToOptions) {
       this.api = api;
-      this.linkToOptions = linkToOptions;
+      this.hrefToOptions = hrefToOptions;
    }
 
    protected abstract IterableWithMarker<T> fetchPageUsingOptions(O options, Optional<Object> arg0);
@@ -50,7 +50,7 @@ public abstract class BaseToPagedIterable<T, O extends ListOptions> extends
       return new Function<Object, IterableWithMarker<T>>() {
          @Override
          public IterableWithMarker<T> apply(Object input) {
-            O nextOptions = linkToOptions.apply(Href.class.cast(input));
+            O nextOptions = hrefToOptions.apply(Href.class.cast(input));
             return fetchPageUsingOptions(nextOptions, arg0);
          }
       };

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/functions/HrefToListOptions.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/functions/HrefToListOptions.java b/packet/src/main/java/org/jclouds/packet/functions/HrefToListOptions.java
new file mode 100644
index 0000000..d380b26
--- /dev/null
+++ b/packet/src/main/java/org/jclouds/packet/functions/HrefToListOptions.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.functions;
+
+import java.net.URI;
+
+import org.jclouds.packet.domain.Href;
+import org.jclouds.packet.domain.options.ListOptions;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Multimap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.emptyToNull;
+import static com.google.common.collect.Iterables.getFirst;
+import static org.jclouds.http.utils.Queries.queryParser;
+import static org.jclouds.packet.domain.options.ListOptions.PAGE_PARAM;
+import static org.jclouds.packet.domain.options.ListOptions.PER_PAGE_PARAM;
+
+/**
+ * Transforms an href returned by the API into a {@link ListOptions} that can be
+ * used to perform a request to get another page of a paginated list.
+ */
+public class HrefToListOptions implements Function<Href, ListOptions> {
+
+   @Override
+   public ListOptions apply(Href input) {
+      checkNotNull(input, "input cannot be null");
+
+      Multimap<String, String> queryParams = queryParser().apply(URI.create(input.href()).getQuery());
+      String nextPage = getFirstOrNull(PAGE_PARAM, queryParams);
+      String nextPerPage = getFirstOrNull(PER_PAGE_PARAM, queryParams);
+
+      ListOptions options = new ListOptions();
+      if (nextPage != null) {
+         options.page(Integer.parseInt(nextPage));
+      }
+      if (nextPerPage != null) {
+         options.perPage(Integer.parseInt(nextPerPage));
+      }
+
+      return options;
+   }
+
+   public static String getFirstOrNull(String key, Multimap<String, String> params) {
+      return params.containsKey(key) ? emptyToNull(getFirst(params.get(key), null)) : null;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java
----------------------------------------------------------------------
diff --git a/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java b/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java
deleted file mode 100644
index 4aef811..0000000
--- a/packet/src/main/java/org/jclouds/packet/functions/LinkToListOptions.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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.packet.functions;
-
-import java.net.URI;
-
-import org.jclouds.packet.domain.Href;
-import org.jclouds.packet.domain.options.ListOptions;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Multimap;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Strings.emptyToNull;
-import static com.google.common.collect.Iterables.getFirst;
-import static org.jclouds.http.utils.Queries.queryParser;
-import static org.jclouds.packet.domain.options.ListOptions.PAGE_PARAM;
-import static org.jclouds.packet.domain.options.ListOptions.PER_PAGE_PARAM;
-
-/**
- * Transforms an href returned by the API into a {@link ListOptions} that can be
- * used to perform a request to get another page of a paginated list.
- */
-public class LinkToListOptions implements Function<Href, ListOptions> {
-
-   @Override
-   public ListOptions apply(Href input) {
-      checkNotNull(input, "input cannot be null");
-
-      Multimap<String, String> queryParams = queryParser().apply(URI.create(input.href()).getQuery());
-      String nextPage = getFirstOrNull(PAGE_PARAM, queryParams);
-      String nextPerPage = getFirstOrNull(PER_PAGE_PARAM, queryParams);
-
-      ListOptions options = new ListOptions();
-      if (nextPage != null) {
-         options.page(Integer.parseInt(nextPage));
-      }
-      if (nextPerPage != null) {
-         options.perPage(Integer.parseInt(nextPerPage));
-      }
-
-      return options;
-   }
-
-   public static String getFirstOrNull(String key, Multimap<String, String> params) {
-      return params.containsKey(key) ? emptyToNull(getFirst(params.get(key), null)) : null;
-   }
-
-}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java
new file mode 100644
index 0000000..36c08f1
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/DeviceApiLiveTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.packet.compute.internal.BasePacketApiLiveTest;
+import org.jclouds.packet.domain.ActionType;
+import org.jclouds.packet.domain.BillingCycle;
+import org.jclouds.packet.domain.Device;
+import org.jclouds.packet.domain.SshKey;
+import org.jclouds.ssh.SshKeys;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "DeviceApiLiveTest")
+public class DeviceApiLiveTest extends BasePacketApiLiveTest {
+
+   private SshKey sshKey;
+   private String deviceId;
+
+   @BeforeClass
+   public void setupDevice() {
+      Map<String, String> keyPair = SshKeys.generate();
+      sshKey = api.sshKeyApi().create(prefix + "-device-livetest", keyPair.get("public"));
+   }
+
+   @AfterClass(alwaysRun = true)
+   public void tearDown() {
+      if (sshKey != null) {
+         api.sshKeyApi().delete(sshKey.id());
+      }
+   }
+
+   public void testCreate() {
+      Device deviceCreated = api().create(
+              Device.CreateDevice.builder()
+                      .hostname(prefix + "-device-livetest")
+                      .plan("baremetal_0")
+                      .billingCycle(BillingCycle.HOURLY.value())
+                      .facility("ewr1")
+                      .features(ImmutableMap.<String, String>of())
+                      .operatingSystem("ubuntu_16_04")
+                      .locked(false)
+                      .userdata("")
+                      .tags(ImmutableSet.<String> of())
+                      .build()
+      );
+      deviceId = deviceCreated.id();
+      assertNodeRunning(deviceId);
+      Device device = api().get(deviceId);
+      assertNotNull(device, "Device must not be null");
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testCreate")
+   public void testReboot() {
+      api().actions(deviceId, ActionType.REBOOT);
+      assertNodeRunning(deviceId);
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testReboot")
+   public void testPowerOff() {
+      api().actions(deviceId, ActionType.POWER_OFF);
+      assertNodeTerminated(deviceId);
+   }
+
+   @Test(groups = "live", dependsOnMethods = "testPowerOff")
+   public void testPowerOn() {
+      api().actions(deviceId, ActionType.POWER_ON);
+      assertNodeRunning(deviceId);
+   }
+   
+   @Test(dependsOnMethods = "testCreate")
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list().concat(), new Predicate<Device>() {
+         @Override
+         public boolean apply(Device input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All devices must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some devices to be returned");
+   }
+
+   @Test(dependsOnMethods = "testCreate")
+   public void testListOnePage() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Device>() {
+         @Override
+         public boolean apply(Device input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All devices must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some devices to be returned");
+   }
+
+   @Test(dependsOnMethods = "testList", alwaysRun = true)
+   public void testDelete() throws InterruptedException {
+      if (deviceId != null) {
+         api().delete(deviceId);
+         assertNodeTerminated(deviceId);
+         assertNull(api().get(deviceId));
+      }
+   }
+
+   private DeviceApi api() {
+      return api.deviceApi(identity);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java b/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java
new file mode 100644
index 0000000..705955c
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/DeviceApiMockTest.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
+import org.jclouds.packet.domain.ActionType;
+import org.jclouds.packet.domain.BillingCycle;
+import org.jclouds.packet.domain.Device;
+import org.jclouds.packet.domain.Device.CreateDevice;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "DeviceApiMockTest", singleThreaded = true)
+public class DeviceApiMockTest extends BasePacketApiMockTest {
+
+   public void testListDevices() throws InterruptedException {
+      server.enqueue(jsonResponse("/devices-first.json"));
+      server.enqueue(jsonResponse("/devices-last.json"));
+
+      Iterable<Device> devices = api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").list().concat();
+
+      assertEquals(size(devices), 7); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices");
+      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=2");
+   }
+
+   public void testListDevicesReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Device> devices = api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").list().concat();
+
+      assertTrue(isEmpty(devices));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices");
+   }
+
+   public void testListDevicesWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/devices-first.json"));
+
+      Iterable<Device> devices = api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").list(page(1).perPage(5));
+
+      assertEquals(size(devices), 5);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=1&per_page=5");
+   }
+
+   public void testListDevicesWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Device> actions = api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").list(page(1).perPage(5));
+
+      assertTrue(isEmpty(actions));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices?page=1&per_page=5");
+   }
+
+   public void testGetDevice() throws InterruptedException {
+      server.enqueue(jsonResponse("/device.json"));
+
+      Device device = api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").get("1");
+
+      assertEquals(device, objectFromResource("/device.json", Device.class));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+   }
+
+   public void testGetDeviceReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Device device = api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").get("1");
+
+      assertNull(device);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+   }
+
+   public void testCreateDevice() throws InterruptedException {
+      server.enqueue(jsonResponse("/device-create-res.json"));
+
+      Device device = api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").create(
+              CreateDevice.builder()
+                      .hostname("jclouds-device-livetest")
+                      .plan("baremetal_0")
+                      .billingCycle(BillingCycle.HOURLY.value())
+                      .facility("ewr1")
+                      .features(ImmutableMap.<String, String>of())
+                      .operatingSystem("ubuntu_16_04")
+                      .locked(false)
+                      .userdata("")
+                      .tags(ImmutableSet.<String> of())
+                      .build()
+      );
+
+      assertEquals(device, objectFromResource("/device-create-res.json", Device.class));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices", stringFromResource("/device-create-req.json"));
+   }
+
+   public void testDeleteDevice() throws InterruptedException {
+      server.enqueue(response204());
+
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").delete("1");
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+   }
+
+   public void testDeleteDeviceReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").delete("1");
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "DELETE", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/1");
+   }
+
+   public void testActionPowerOn() throws InterruptedException {
+      server.enqueue(jsonResponse("/power-on.json"));
+
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.POWER_ON);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
+   }
+
+   public void testActionPowerOff() throws InterruptedException {
+      server.enqueue(jsonResponse("/power-off.json"));
+
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.POWER_OFF);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
+   }
+
+   public void testActionReboot() throws InterruptedException {
+      server.enqueue(jsonResponse("/reboot.json"));
+
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.REBOOT);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
+   }
+
+   public void testActionRescue() throws InterruptedException {
+      server.enqueue(jsonResponse("/rescue.json"));
+
+      api.deviceApi("93907f48-adfe-43ed-ad89-0e6e83721a54").actions("deviceId", ActionType.RESCUE);
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "POST", "/projects/93907f48-adfe-43ed-ad89-0e6e83721a54/devices/deviceId/actions");
+   }
+   
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java
new file mode 100644
index 0000000..95fc857
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/FacilityApiLiveTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
+import org.jclouds.packet.domain.Facility;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "FacilityApiLiveTest")
+public class FacilityApiLiveTest extends BasePacketApiMockTest {
+
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list().concat(), new Predicate<Facility>() {
+         @Override
+         public boolean apply(Facility input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All facilities must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some facilities to be returned");
+   }
+
+   public void testListOnePage() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Facility>() {
+         @Override
+         public boolean apply(Facility input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All facilities must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some facilities to be returned");
+   }
+
+   private FacilityApi api() {
+      return api.facilityApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/FacilityApiMockTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/FacilityApiMockTest.java b/packet/src/test/java/org/jclouds/packet/features/FacilityApiMockTest.java
new file mode 100644
index 0000000..764fa7b
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/FacilityApiMockTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
+import org.jclouds.packet.domain.Facility;
+import org.testng.annotations.Test;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "FacilityApiMockTest", singleThreaded = true)
+public class FacilityApiMockTest extends BasePacketApiMockTest {
+
+   public void testListFacilities() throws InterruptedException {
+      server.enqueue(jsonResponse("/facilities-first.json"));
+      server.enqueue(jsonResponse("/facilities-last.json"));
+
+      Iterable<Facility> facilities = api.facilityApi().list().concat();
+
+      assertEquals(size(facilities), 3); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/facilities");
+      assertSent(server, "GET", "/facilities?page=2");
+   }
+
+   public void testListFacilitiesReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Facility> facilities = api.facilityApi().list().concat();
+
+      assertTrue(isEmpty(facilities));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/facilities");
+   }
+
+   public void testListFacilitiesWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/facilities-first.json"));
+
+      Iterable<Facility> actions = api.facilityApi().list(page(1).perPage(2));
+
+      assertEquals(size(actions), 2);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/facilities?page=1&per_page=2");
+   }
+
+   public void testListFacilitiesWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Facility> actions = api.facilityApi().list(page(1).perPage(2));
+
+      assertTrue(isEmpty(actions));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/facilities?page=1&per_page=2");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiLiveTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiLiveTest.java
new file mode 100644
index 0000000..fd96a7e
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiLiveTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.packet.compute.internal.BasePacketApiLiveTest;
+import org.jclouds.packet.domain.OperatingSystem;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "OperatingSystemApiLiveTest")
+public class OperatingSystemApiLiveTest extends BasePacketApiLiveTest {
+
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list().concat(), new Predicate<OperatingSystem>() {
+         @Override
+         public boolean apply(OperatingSystem input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All operating systems must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some operating systems to be returned");
+   }
+
+   public void testListOnePage() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<OperatingSystem>() {
+         @Override
+         public boolean apply(OperatingSystem input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All operating systems must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some operating systems to be returned");
+   }
+
+   private OperatingSystemApi api() {
+      return api.operatingSystemApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiMockTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiMockTest.java b/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiMockTest.java
new file mode 100644
index 0000000..c0c332b
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/OperatingSystemApiMockTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
+import org.jclouds.packet.domain.OperatingSystem;
+import org.testng.annotations.Test;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "OperatingSystemApiMockTest", singleThreaded = true)
+public class OperatingSystemApiMockTest extends BasePacketApiMockTest {
+
+   public void testListOperatingSystems() throws InterruptedException {
+
+      server.enqueue(jsonResponse("/operatingSystems-first.json"));
+      server.enqueue(jsonResponse("/operatingSystems-last.json"));
+
+      Iterable<OperatingSystem> operatingSystems = api.operatingSystemApi().list().concat();
+      assertEquals(size(operatingSystems), 14); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/operating-systems");
+      assertSent(server, "GET", "/operating-systems?page=2");
+   }
+
+   public void testListOperatingSystemsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<OperatingSystem> operatingSystems = api.operatingSystemApi().list().concat();
+
+      assertTrue(isEmpty(operatingSystems));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/operating-systems");
+   }
+
+   public void testListOperatingSystemsWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/operatingSystems-first.json"));
+
+      Iterable<OperatingSystem> operatingSystems = api.operatingSystemApi().list(page(1).perPage(5));
+
+      assertEquals(size(operatingSystems), 7);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/operating-systems?page=1&per_page=5");
+   }
+
+   public void testListOperatingSystemsWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<OperatingSystem> operatingSystems = api.operatingSystemApi().list(page(1).perPage(5));
+
+      assertTrue(isEmpty(operatingSystems));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/operating-systems?page=1&per_page=5");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/PlanApiLiveTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/PlanApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/PlanApiLiveTest.java
new file mode 100644
index 0000000..1b66e04
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/PlanApiLiveTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jclouds.packet.compute.internal.BasePacketApiLiveTest;
+import org.jclouds.packet.domain.Plan;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertTrue;
+import static org.testng.util.Strings.isNullOrEmpty;
+
+@Test(groups = "live", testName = "PlanApiLiveTest")
+public class PlanApiLiveTest extends BasePacketApiLiveTest {
+
+   public void testList() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(Iterables.all(api().list().concat(), new Predicate<Plan>() {
+         @Override
+         public boolean apply(Plan input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All plans must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some plans to be returned");
+   }
+
+   public void testListOnePage() {
+      final AtomicInteger found = new AtomicInteger(0);
+      assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Plan>() {
+         @Override
+         public boolean apply(Plan input) {
+            found.incrementAndGet();
+            return !isNullOrEmpty(input.id());
+         }
+      }), "All plans must have the 'id' field populated");
+      assertTrue(found.get() > 0, "Expected some plans to be returned");
+   }
+
+   private PlanApi api() {
+      return api.planApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/PlanApiMockTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/PlanApiMockTest.java b/packet/src/test/java/org/jclouds/packet/features/PlanApiMockTest.java
new file mode 100644
index 0000000..82683c8
--- /dev/null
+++ b/packet/src/test/java/org/jclouds/packet/features/PlanApiMockTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.packet.features;
+
+import org.jclouds.packet.compute.internal.BasePacketApiMockTest;
+import org.jclouds.packet.domain.Plan;
+import org.testng.annotations.Test;
+
+import static com.google.common.collect.Iterables.isEmpty;
+import static com.google.common.collect.Iterables.size;
+import static org.jclouds.packet.domain.options.ListOptions.Builder.page;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test(groups = "unit", testName = "PlanApiMockTest", singleThreaded = true)
+public class PlanApiMockTest extends BasePacketApiMockTest {
+
+   public void testListPlans() throws InterruptedException {
+      server.enqueue(jsonResponse("/plans-first.json"));
+      server.enqueue(jsonResponse("/plans-last.json"));
+
+      Iterable<Plan> plans = api.planApi().list().concat();
+
+      assertEquals(size(plans), 7); // Force the PagedIterable to advance
+      assertEquals(server.getRequestCount(), 2);
+
+      assertSent(server, "GET", "/plans");
+      assertSent(server, "GET", "/plans?page=2");
+   }
+
+   public void testListPlansReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Plan> plans = api.planApi().list().concat();
+
+      assertTrue(isEmpty(plans));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/plans");
+   }
+
+   public void testListPlansWithOptions() throws InterruptedException {
+      server.enqueue(jsonResponse("/plans-first.json"));
+
+      Iterable<Plan> plans = api.planApi().list(page(1).perPage(5));
+
+      assertEquals(size(plans), 4);
+      assertEquals(server.getRequestCount(), 1);
+
+      assertSent(server, "GET", "/plans?page=1&per_page=5");
+   }
+
+   public void testListPlansWithOptionsReturns404() throws InterruptedException {
+      server.enqueue(response404());
+
+      Iterable<Plan> plans = api.planApi().list(page(1).perPage(5));
+
+      assertTrue(isEmpty(plans));
+
+      assertEquals(server.getRequestCount(), 1);
+      assertSent(server, "GET", "/plans?page=1&per_page=5");
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java b/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java
index 133e5ef..65fba8f 100644
--- a/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java
+++ b/packet/src/test/java/org/jclouds/packet/features/ProjectApiLiveTest.java
@@ -32,7 +32,7 @@ import static org.testng.util.Strings.isNullOrEmpty;
 @Test(groups = "live", testName = "ProjectApiLiveTest")
 public class ProjectApiLiveTest extends BasePacketApiLiveTest {
 
-   public void testListProjects() {
+   public void testList() {
       final AtomicInteger found = new AtomicInteger(0);
       assertTrue(Iterables.all(api().list().concat(), new Predicate<Project>() {
          @Override
@@ -43,8 +43,8 @@ public class ProjectApiLiveTest extends BasePacketApiLiveTest {
       }), "All projects must have the 'id' field populated");
       assertTrue(found.get() > 0, "Expected some projects to be returned");
    }
-   
-   public void testListActionsOnePage() {
+
+   public void testListOnePage() {
       final AtomicInteger found = new AtomicInteger(0);
       assertTrue(api().list(page(1).perPage(5)).allMatch(new Predicate<Project>() {
          @Override
@@ -55,7 +55,6 @@ public class ProjectApiLiveTest extends BasePacketApiLiveTest {
       }), "All projects must have the 'id' field populated");
       assertTrue(found.get() > 0, "Expected some projects to be returned");
    }
-   
 
    private ProjectApi api() {
       return api.projectApi();

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/f749c88e/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java
----------------------------------------------------------------------
diff --git a/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java b/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java
index 2899020..d972395 100644
--- a/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java
+++ b/packet/src/test/java/org/jclouds/packet/features/ProjectApiMockTest.java
@@ -74,6 +74,5 @@ public class ProjectApiMockTest extends BasePacketApiMockTest {
       assertEquals(server.getRequestCount(), 1);
       assertSent(server, "GET", "/projects?page=1&per_page=5");
    }
-   
 
 }