From 36cc3805b5d66c91a2faa959bf4e4e54d92aa266 Mon Sep 17 00:00:00 2001 From: Mattias Holmqvist Date: Thu, 2 Jun 2011 12:11:23 +0200 Subject: [PATCH 1/4] First version of ebs2.clj --- .../src/main/clojure/org/jclouds/ec2/ebs2.clj | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj diff --git a/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj b/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj new file mode 100644 index 0000000000..854bdb36fa --- /dev/null +++ b/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj @@ -0,0 +1,290 @@ +; +; +; Copyright (C) 2011 Cloud Conscious, LLC. +; +; ==================================================================== +; Licensed 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. +; ==================================================================== +; + +(ns + #^{:author "Chas Emerick, cemerick@snowtide.com" + :doc "A clojure binding to the jclouds EBS service interface."} + org.jclouds.ec2.ebs2 + (:use [org.jclouds.compute2]) + (:use (clojure.contrib def core)) + (:import org.jclouds.aws.domain.Region + org.jclouds.compute.domain.NodeMetadata + (org.jclouds.ec2.domain Volume Volume$Status Snapshot Snapshot$Status AvailabilityZoneInfo) + (org.jclouds.ec2.options DescribeSnapshotsOptions DetachVolumeOptions CreateSnapshotOptions))) +(defn snapshot? + "Returns true iff the argument is a org.jclouds.ec2.domain.Snapshot." + [s] + (instance? Snapshot s)) + +(defn volume? + "Returns true iff the argument is a org.jclouds.ec2.domain.Volume." + [v] + (instance? Volume v)) + +(defn #^org.jclouds.ec2.services.ElasticBlockStoreClient + ebs-service + "" + [compute] + (-> compute + .getContext + .getProviderSpecificContext + .getApi + .getElasticBlockStoreServices)) + +(defn get-region + "Coerces the first parameter into a Region string; strings, keywords, and + NodeMetadata instances are acceptable arguments. An optional second argument + is returned if the first cannot be coerced into a region string. + Returns nil otherwise." + ([v] (get-region v nil)) + ([v default-region] + (cond + (string? v) v + (keyword? v) (name v) + (instance? NodeMetadata v) (let [zone (location v)] + ; no easier way to go from zone -> region? + (if (> (.indexOf zone "-") -1) + (subs zone 0 (-> zone count dec)) + zone)) + :else default-region))) + +(defn get-volume-id + "Returns a string volume ID taken from the given string, keyword, or Volume argument." + [v] + (cond + (instance? Volume v) (.getId #^Volume v) + (keyword? v) (name v) + (string? v) v + :else (throw (IllegalArgumentException. + (str "Can't obtain volume id from argument of type " (class v)))))) + +(defn volumes + "Returns a set of org.jclouds.ec2.domain.Volume instances corresponding to the + volumes in the specified region (defaulting to your account's default region)." + + [compute & [region & volume-ids]] + (set + (.describeVolumesInRegion (ebs-service compute) + (get-region region) + (into-array String (map get-volume-id + (if (get-region region) + volume-ids + (when region (cons region volume-ids)))))))) +(defn- as-string + [v] + (cond + (string? v) v + (keyword? v) (name v) + :else v)) +(defn- get-string + [map key] + (as-string (get map key))) +(defn- as-int + [v] + (cond + (number? v) (int v) + (string? v) (Integer/parseInt v) + :else (throw (IllegalArgumentException. + (str "Don't know how to convert object of type " (class v) " to a string"))))) + +(defn- snapshot-options + [optmap] + (let [string-array #(let [v (% optmap)] + (into-array String (cond + (keyword? v) [(name v)] + (string? v) [v] + :else (map as-string v))))] + (-> (DescribeSnapshotsOptions.) + (.ownedBy (string-array :owner)) + (.snapshotIds (string-array :ids)) + (.restorableBy (string-array :restorable-by))))) + +(defn snapshots + "Returns a set of org.jclouds.aws.ec2.domain.Snapshot instances that match + the criteria provided. Options include: + + :region - region string, keyword, or NodeMetadata + :owner - AWS account id (or \"amazon\" or \"self\") + :restorable-by - AWS account id + + Multiple values for each type of criteria can be provided by passing a seq + of the appropriate types as values." + [compute & options] + (let [options (apply hash-map options) + region (:region options) + options (snapshot-options (dissoc options :region))] + (set + (.describeSnapshotsInRegion (ebs-service compute) + (get-region region) + (into-array DescribeSnapshotsOptions [options]))))) + +(defn create-snapshot + "Creates a snapshot of a volume in the specified region with an optional description. + If provided, the description must be < 255 characters in length. Returns the + org.jclouds.aws.ec2.domain.Snapshot object representing the created snapshot." + + ([computess #^Volume volume] (create-snapshot computess volume nil)) + ([compute #^Volume volume description] (create-snapshot compute (.getRegion volume) (.getId volume) description)) + ([compute region volume-id description] + (.createSnapshotInRegion (ebs-service compute) + (get-region region) + (as-string volume-id) + (into-array CreateSnapshotOptions (when description + [(.withDescription (CreateSnapshotOptions.) description)]))))) + +(defn delete-snapshot + "Deletes a snapshot in the specified region." + ([compute #^Snapshot snapshot] (delete-snapshot compute (.getRegion snapshot) (.getId snapshot))) + ([compute region snapshot-id] + (.deleteSnapshotInRegion (ebs-service compute) + (get-region region) + (as-string snapshot-id)))) + +(defn get-zone + [v] + (cond + (instance? AvailabilityZoneInfo v) (.getZone v) + (instance? NodeMetadata v) (location #^NodeMetadata v) + (string? v) v + (keyword? v) (name v) + :else (throw (IllegalArgumentException. + (str "Can't obtain zone from argument of type " (class v)))))) + +(defn attach-volume + "Attaches a volume to an instance, returning the resulting org.jclouds.aws.ec2.domain.Attachment." + ([compute #^NodeMetadata node volume device] + (attach-volume compute node (.getProviderId node) (get-volume-id volume) device)) + ([compute region instance-id volume-id device] + (apply #(.attachVolumeInRegion (ebs-service compute) + (get-region region) % %2 %3) + (map as-string [volume-id instance-id device])))) + +(defn detach-volume + "Detaches a volume from the instance to which it is currently attached. + The volume may be specified with a Volume instance, a string, or a keyword. + Providing a logical true value for the :force option will cause the volume + to be forcibly detached, regardless of whether it is in-use (mounted) or not. + + If the volume is specified as a string or keyword, one of the following options + is additionally required: + + :region - the region where the volume is allocated + :node - a node in the region where the volume is allocated + + FYI: It appears that issuing a detatch-volume command while the volume in question is mounted + will cause the volume to be detatched immediately upon the volume beign unmounted." + [compute volume & options] + (let [options (apply hash-map options) + volume-id (get-volume-id volume) + region (get-region (if (instance? Volume volume) + (.getRegion volume) + (or (:region options) (:node options))))] + (when (not region) + (throw (IllegalArgumentException. + "Must specify volume's region via :region or :node options, or by providing a Volume instance."))) + (.detachVolumeInRegion (ebs-service compute) + region + volume-id + (boolean (:force options)) + (into-array DetachVolumeOptions [])))) + +(defn create-volume + "Creates a new volume given a set of options: + + - one of :zone (keyword, string, or AvailabilityZoneInfo) or :node (NodeMetadata) + - one or both of :snapshot (keyword, string, or Snapshot instance) or :size + (string, keyword, or number) + - :device (string or keyword) provided *only* when you want to attach the new volume to + the :node you specified! + + Returns a vector of [created org.jclouds.ec2.domain.Volume, + optional org.jclouds.ec2.domain.Attachment] + + Note that specifying :node instead of :zone will only attach the created volume + :device is also provided. Otherwise, the node is only used to obtain the desired + availability zone. + + Note also that if :device and :node are specified, and the attach operation fails, + you will have \"leaked\" the newly-created volume + (volume creation and attachment cannot be done atomically)." + + [compute & options] + (when (-> options count odd?) + (throw (IllegalArgumentException. "Must provide key-value pairs, e.g. :zone :us-east-1d :size 200"))) + (let [options (apply hash-map options) + snapshot (get-string options :snapshot) + snapshot (if (snapshot? snapshot) (.getId snapshot) snapshot) + size (-?> (get-string options :size) as-int) + #^NodeMetadata node (:node options) + zone (or node (get-string options :zone)) + zone (if zone + (get-zone zone) + (throw (IllegalArgumentException. "Must supply a :zone or :node option."))) + ebs (ebs-service compute)] + (when (and (:device options) (not node)) + (throw (IllegalArgumentException. "Cannot create and attach new volume; no :node specified"))) + (let [new-volume (cond + (and snapshot size) (.createVolumeFromSnapshotInAvailabilityZone ebs zone size snapshot) + snapshot (.createVolumeFromSnapshotInAvailabilityZone ebs zone snapshot) + size (.createVolumeInAvailabilityZone ebs zone size) + :else (throw (IllegalArgumentException. "Must supply :size and/or :snapshot options.")))] + [new-volume (when (:device options) + (attach-volume compute node new-volume (as-string (:device options))))]))) + +(defn delete-volume + "Deletes a volume in the specified region." + ([compute #^Volume volume] + (delete-volume (.getRegion volume) (.getId volume))) + ([compute region volume-id] + (.deleteVolumeInRegion (ebs-service compute) + (get-region region) + (as-string volume-id)))) + +(defn status + "Returns the status of the given entity; works for Volumes and Snapshots." + [k] + (.getStatus k)) + +(defn status-available? + [#^Volume v] + (= Volume$Status/AVAILABLE (status v))) + +(defn status-creating? + [#^Volume v] + (= Volume$Status/CREATING (status v))) + +(defn status-deleting? + [#^Volume v] + (= Volume$Status/DELETING (status v))) + +(defn status-in-use? + [#^Volume v] + (= Volume$Status/IN_USE (status v))) + +(defn status-completed? + [#^Snapshot s] + (= Snapshot$Status/COMPLETED (status s))) + +(defn status-error? + [#^Snapshot s] + (= Snapshot$Status/ERROR (status s))) + +(defn status-pending? + [#^Snapshot s] + (= Snapshot$Status/PENDING (status s))) \ No newline at end of file From f73b46965b31d3847a5da8015099dd27b857a66f Mon Sep 17 00:00:00 2001 From: Mattias Holmqvist Date: Thu, 2 Jun 2011 12:45:52 +0200 Subject: [PATCH 2/4] First version of elastic_ip2.clj --- .../clojure/org/jclouds/ec2/elastic_ip2.clj | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj diff --git a/apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj b/apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj new file mode 100644 index 0000000000..660f4db498 --- /dev/null +++ b/apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj @@ -0,0 +1,84 @@ +; +; +; Copyright (C) 2011 Cloud Conscious, LLC. +; +; ==================================================================== +; Licensed 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. +; ==================================================================== +; + +(ns + #^{:author "Chas Emerick, cemerick@snowtide.com" + :doc "A clojure binding for the jclouds AWS elastic IP address interface."} + org.jclouds.ec2.elastic-ip2 + (:require (org.jclouds [compute2 :as compute]) + [org.jclouds.ec2.ebs :as ebs]) + (:use (clojure.contrib def core)) + (:import org.jclouds.compute.domain.NodeMetadata + (org.jclouds.ec2.domain PublicIpInstanceIdPair))) + +(defn #^org.jclouds.ec2.services.ElasticIPAddressClient + eip-service + "Returns the synchronous ElasticIPAddressClient associated with + the specified compute service, or compute/*compute* as bound by with-compute-service." + [compute] + (-> compute + .getContext .getProviderSpecificContext .getApi .getElasticIPAddressServices)) + +(defn allocate + "Claims a new elastic IP address within the (optionally) specified region for your account. + Region may be a string, keyword, or a node from which the region + is inferred. Returns the IP address as a string." + ([compute] (allocate compute nil)) + ([compute region] + (.allocateAddressInRegion (eip-service compute) (ebs/get-region region)))) + +(defn associate + "Associates an elastic IP address with a node." + ([compute #^NodeMetadata node public-ip] + (associate node public-ip (.getProviderId node))) + ([compute region public-ip instance-id] + (.associateAddressInRegion (eip-service compute) + (ebs/get-region region) + public-ip + instance-id))) + +(defn addresses + "Returns a map of elastic IP addresses to maps with slots: + + :region - the region (string/keyword/NodeMetadata) the IP address is allocated within + :node-id - the ID of the instance with which the IP address is associated (optional) + + You may optionally specify which IP addresses you would like to query." + ([compute] (addresses compute nil)) + ([compute region & public-ips] + (into {} (for [#^PublicIpInstanceIdPair pair (.describeAddressesInRegion (eip-service compute) + (ebs/get-region region) + (into-array String public-ips))] + [(.getPublicIp pair) (merge {:region (.getRegion pair)} + (when (.getInstanceId pair) {:node-id (.getInstanceId pair)}))])))) + +(defn dissociate + "Dissociates an elastic IP address from the node with which it is currently associated." + [compute region public-ip] + (.disassociateAddressInRegion (eip-service compute) + (ebs/get-region region) + public-ip)) + +(defn release + "Disclaims an elastic IP address from your account." + ([compute public-ip] (release compute nil public-ip)) + ([compute region public-ip] + (.releaseAddressInRegion (eip-service compute) + (ebs/get-region region) + public-ip))) From c330e100f0c574e3b2a2ebe7a1e5fe32338e2d4b Mon Sep 17 00:00:00 2001 From: Mattias Holmqvist Date: Thu, 2 Jun 2011 14:00:02 +0200 Subject: [PATCH 3/4] Fixed typo --- apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj b/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj index 854bdb36fa..ed7be97a54 100644 --- a/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj +++ b/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj @@ -139,7 +139,7 @@ If provided, the description must be < 255 characters in length. Returns the org.jclouds.aws.ec2.domain.Snapshot object representing the created snapshot." - ([computess #^Volume volume] (create-snapshot computess volume nil)) + ([compute #^Volume volume] (create-snapshot compute volume nil)) ([compute #^Volume volume description] (create-snapshot compute (.getRegion volume) (.getId volume) description)) ([compute region volume-id description] (.createSnapshotInRegion (ebs-service compute) From 69aac6b455a1a50037081e0dda1018d84a5304f1 Mon Sep 17 00:00:00 2001 From: Mattias Holmqvist Date: Fri, 3 Jun 2011 09:29:04 +0200 Subject: [PATCH 4/4] Cleaning up API for ebs2 and elastic-ip2 after review. Minor changes to doc-strings, code formatting and order of args in release fn. --- .../src/main/clojure/org/jclouds/ec2/ebs2.clj | 37 +++++++++---------- .../clojure/org/jclouds/ec2/elastic_ip2.clj | 13 +++---- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj b/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj index ed7be97a54..1252090ddd 100644 --- a/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj +++ b/apis/ec2/src/main/clojure/org/jclouds/ec2/ebs2.clj @@ -37,7 +37,7 @@ [v] (instance? Volume v)) -(defn #^org.jclouds.ec2.services.ElasticBlockStoreClient +(defn ^org.jclouds.ec2.services.ElasticBlockStoreClient ebs-service "" [compute] @@ -68,7 +68,7 @@ "Returns a string volume ID taken from the given string, keyword, or Volume argument." [v] (cond - (instance? Volume v) (.getId #^Volume v) + (instance? Volume v) (.getId ^Volume v) (keyword? v) (name v) (string? v) v :else (throw (IllegalArgumentException. @@ -77,7 +77,6 @@ (defn volumes "Returns a set of org.jclouds.ec2.domain.Volume instances corresponding to the volumes in the specified region (defaulting to your account's default region)." - [compute & [region & volume-ids]] (set (.describeVolumesInRegion (ebs-service compute) @@ -86,6 +85,7 @@ (if (get-region region) volume-ids (when region (cons region volume-ids)))))))) + (defn- as-string [v] (cond @@ -95,6 +95,7 @@ (defn- get-string [map key] (as-string (get map key))) + (defn- as-int [v] (cond @@ -138,9 +139,8 @@ "Creates a snapshot of a volume in the specified region with an optional description. If provided, the description must be < 255 characters in length. Returns the org.jclouds.aws.ec2.domain.Snapshot object representing the created snapshot." - - ([compute #^Volume volume] (create-snapshot compute volume nil)) - ([compute #^Volume volume description] (create-snapshot compute (.getRegion volume) (.getId volume) description)) + ([compute ^Volume volume] (create-snapshot compute volume nil)) + ([compute ^Volume volume description] (create-snapshot compute (.getRegion volume) (.getId volume) description)) ([compute region volume-id description] (.createSnapshotInRegion (ebs-service compute) (get-region region) @@ -150,7 +150,7 @@ (defn delete-snapshot "Deletes a snapshot in the specified region." - ([compute #^Snapshot snapshot] (delete-snapshot compute (.getRegion snapshot) (.getId snapshot))) + ([compute ^Snapshot snapshot] (delete-snapshot compute (.getRegion snapshot) (.getId snapshot))) ([compute region snapshot-id] (.deleteSnapshotInRegion (ebs-service compute) (get-region region) @@ -160,7 +160,7 @@ [v] (cond (instance? AvailabilityZoneInfo v) (.getZone v) - (instance? NodeMetadata v) (location #^NodeMetadata v) + (instance? NodeMetadata v) (location ^NodeMetadata v) (string? v) v (keyword? v) (name v) :else (throw (IllegalArgumentException. @@ -168,7 +168,7 @@ (defn attach-volume "Attaches a volume to an instance, returning the resulting org.jclouds.aws.ec2.domain.Attachment." - ([compute #^NodeMetadata node volume device] + ([compute ^NodeMetadata node volume device] (attach-volume compute node (.getProviderId node) (get-volume-id volume) device)) ([compute region instance-id volume-id device] (apply #(.attachVolumeInRegion (ebs-service compute) @@ -223,7 +223,6 @@ Note also that if :device and :node are specified, and the attach operation fails, you will have \"leaked\" the newly-created volume (volume creation and attachment cannot be done atomically)." - [compute & options] (when (-> options count odd?) (throw (IllegalArgumentException. "Must provide key-value pairs, e.g. :zone :us-east-1d :size 200"))) @@ -231,7 +230,7 @@ snapshot (get-string options :snapshot) snapshot (if (snapshot? snapshot) (.getId snapshot) snapshot) size (-?> (get-string options :size) as-int) - #^NodeMetadata node (:node options) + ^NodeMetadata node (:node options) zone (or node (get-string options :zone)) zone (if zone (get-zone zone) @@ -249,7 +248,7 @@ (defn delete-volume "Deletes a volume in the specified region." - ([compute #^Volume volume] + ([compute ^Volume volume] (delete-volume (.getRegion volume) (.getId volume))) ([compute region volume-id] (.deleteVolumeInRegion (ebs-service compute) @@ -262,29 +261,29 @@ (.getStatus k)) (defn status-available? - [#^Volume v] + [^Volume v] (= Volume$Status/AVAILABLE (status v))) (defn status-creating? - [#^Volume v] + [^Volume v] (= Volume$Status/CREATING (status v))) (defn status-deleting? - [#^Volume v] + [^Volume v] (= Volume$Status/DELETING (status v))) (defn status-in-use? - [#^Volume v] + [^Volume v] (= Volume$Status/IN_USE (status v))) (defn status-completed? - [#^Snapshot s] + [^Snapshot s] (= Snapshot$Status/COMPLETED (status s))) (defn status-error? - [#^Snapshot s] + [^Snapshot s] (= Snapshot$Status/ERROR (status s))) (defn status-pending? - [#^Snapshot s] + [^Snapshot s] (= Snapshot$Status/PENDING (status s))) \ No newline at end of file diff --git a/apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj b/apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj index 660f4db498..ee53f25fe9 100644 --- a/apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj +++ b/apis/ec2/src/main/clojure/org/jclouds/ec2/elastic_ip2.clj @@ -27,10 +27,9 @@ (:import org.jclouds.compute.domain.NodeMetadata (org.jclouds.ec2.domain PublicIpInstanceIdPair))) -(defn #^org.jclouds.ec2.services.ElasticIPAddressClient +(defn ^org.jclouds.ec2.services.ElasticIPAddressClient eip-service - "Returns the synchronous ElasticIPAddressClient associated with - the specified compute service, or compute/*compute* as bound by with-compute-service." + "Returns an ElasticIPAddressClient for the given ComputeService" [compute] (-> compute .getContext .getProviderSpecificContext .getApi .getElasticIPAddressServices)) @@ -45,7 +44,7 @@ (defn associate "Associates an elastic IP address with a node." - ([compute #^NodeMetadata node public-ip] + ([compute ^NodeMetadata node public-ip] (associate node public-ip (.getProviderId node))) ([compute region public-ip instance-id] (.associateAddressInRegion (eip-service compute) @@ -62,7 +61,7 @@ You may optionally specify which IP addresses you would like to query." ([compute] (addresses compute nil)) ([compute region & public-ips] - (into {} (for [#^PublicIpInstanceIdPair pair (.describeAddressesInRegion (eip-service compute) + (into {} (for [^PublicIpInstanceIdPair pair (.describeAddressesInRegion (eip-service compute) (ebs/get-region region) (into-array String public-ips))] [(.getPublicIp pair) (merge {:region (.getRegion pair)} @@ -77,8 +76,8 @@ (defn release "Disclaims an elastic IP address from your account." - ([compute public-ip] (release compute nil public-ip)) - ([compute region public-ip] + ([compute public-ip] (release compute public-ip nil)) + ([compute public-ip region] (.releaseAddressInRegion (eip-service compute) (ebs/get-region region) public-ip)))