diff --git a/aws/core/src/main/clojure/org/jclouds/aws/ebs.clj b/aws/core/src/main/clojure/org/jclouds/aws/ebs.clj new file mode 100644 index 0000000000..c2158b2369 --- /dev/null +++ b/aws/core/src/main/clojure/org/jclouds/aws/ebs.clj @@ -0,0 +1,165 @@ +(ns org.jclouds.aws.ebs + "A clojure binding to the jclouds EBS service interface." + (:require (org.jclouds [compute :as compute]) + (clojure.contrib [logging :as log])) + (:use (clojure.contrib def core)) + (:import org.jclouds.aws.domain.Region + org.jclouds.aws.ec2.domain.AvailabilityZone + (org.jclouds.aws.ec2.options DescribeSnapshotsOptions DetachVolumeOptions))) + +(defn #^org.jclouds.aws.ec2.services.ElasticBlockStoreClient + ebs-services + "Returns the synchronous ElasticBlockStoreClient associated with + the specified compute service, or compute/*compute* as bound by with-compute-service." + [& [compute]] + (-> (or compute compute/*compute*) + .getContext .getProviderSpecificContext .getApi .getElasticBlockStoreServices)) + +(defn as-region + "Returns the first argument as the corresponding Region if it is a + keyword or already a Region instance. An optional second argument + is returned if the first cannot be coerced into a Region. + Returns nil otherwise." + [v & [default-region]] + (cond + (keyword? v) (Region/fromValue (name v)) + (instance? Region v) v + :else default-region)) + +(defn describe-volumes + "Returns a set of org.jclouds.aws.ec2.domain.Volume instances corresponding to the + volumes in the specified region (defaulting to your account's default region). + + e.g. (with-compute-service [compute] (describe-volumes)) + (with-compute-service [compute] (describe-volumes :us-east-1 \"vol-6b218805\" ...))" + [& [region & volume-ids]] + (set + (.describeVolumesInRegion (ebs-services) + (as-region region Region/DEFAULT) + (into-array String (if (as-region region) + volume-ids + (cons region volume-ids)))))) + +(defvar- snapshot-options-ops + {:ids #(.snapshotIds % (into-array %2)) + :owners #(.ownedBy % (into-array %2)) + :restorable-by #(.restorableBy % (into-array %2))}) + +(defn- snapshot-options + [options-seq] + (let [optmap (apply hash-map options-seq) + string-array #(let [v (% optmap)] + (into-array String (if (string? v) [v] v)))] + (-> (DescribeSnapshotsOptions.) + (.ownedBy (string-array :owner)) + (.snapshotIds (string-array :ids)) + (.restorableBy (string-array :restorable-by))))) + +(defn describe-snapshots + "Returns a set of org.jclouds.aws.ec2.domain.Snapshot instances corresponding to the + snapshots in the specified region (defaulting to your account's default region) + limited according to the further options provided. + + This returns all of the snapshots you own (you can also provide \"amazon\" or an AWS + account ID here): + (with-compute-service [compute] + (describe-snapshots [:owner \"self\"])) + + This returns metadata on the two specified snapshots in us-west-1 (assuming you have access to them): + (with-compute-service [compute] + (describe-snapshots :us-west-1 [:ids [\"snap-44b3ab2d\" \"snap-9e8821f7\"]])) + + There is also a :restorable-by option. Option values can be provided as strings, or + collections of strings." + [& [region & option-keyvals]] + (let [options (apply snapshot-options + (if (as-region region) + option-keyvals + (cons region option-keyvals)))] + (set + (.describeSnapshotsInRegion (ebs-services) + (as-region region Region/DEFAULT) + (into-array DescribeSnapshotsOptions [options]))))) + +(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 create-volume + "Creates a new volume given a set of options. :zone is required, one + or both of :size and :snapshot may also be provided; all values can be strings or keywords, + :size may be a number, and :zone may be a org.jclouds.aws.ec2.domain.AvailabilityZone. + Returns the created org.jclouds.aws.ec2.domain.Volume. + + e.g. (with-compute-service [compute] + (create-volume :zone :us-east-1a :size 250) + (create-volume :zone :eu-west-1b :snapshot \"snap-252310af\") + (create-volume :zone :eu-west-1b :snapshot \"snap-252310af\" :size :1024))" + [& 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) + size (-?> (get-string options :size) as-int) + zone (get-string options :zone) + zone (if zone + (if (instance? AvailabilityZone zone) + zone + (AvailabilityZone/fromValue zone)) + (throw (IllegalArgumentException. "Must supply a :zone option."))) + ebs (ebs-services)] + (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."))))) + +(defn delete-volume + "Deletes a volume in the specified region. + + e.g. (with-compute-service [compute] + (delete-volume :us-east-1 :vol-45228a6d) + (delete-volume :us-east-1 \"vol-052b846c\"))" + [region volume-id] + (.deleteVolumeInRegion (ebs-services) + (as-region region) + (as-string volume-id))) + +(defn attach-volume + "Attaches a volume to an instance, returning the resulting org.jclouds.aws.ec2.domain.Attachment. + The region must be a keyword or Region instance; the remaining parameters may be + strings or keywords. + + e.g. (with-compute-service [compute] + (attach-volume :us-east-1 :vol-45228a6d \"i-a92358c1\" \"/dev/sdh\"))" + [region & [volume-id instance-id device :as options]] + (apply #(.attachVolumeInRegion (ebs-services) + (as-region region) % %2 %3) + (map as-string options))) + +(defn detach-volume + "Detatches a volume from the instance to which it is currently attached. + The optional force? parameter, if logically true, will cause the volume to be forcibly + detached, regardless of whether it is in-use (mounted) or not. + + (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." + [region volume-id & [force?]] + (.detachVolumeInRegion (ebs-services) + (as-region region) + (as-string volume-id) + (boolean force?) + (into-array DetachVolumeOptions []))) \ No newline at end of file