Issue 226: use jclouds-native objects (mostly) where ever

possible, including adding a single-step create-and-attach option for create-volume
This commit is contained in:
Chas Emerick 2010-04-16 10:57:57 -04:00
parent da9847ab7f
commit e1a5f58bd4
1 changed files with 147 additions and 90 deletions

View File

@ -23,7 +23,8 @@
(:require (org.jclouds [compute :as compute])) (:require (org.jclouds [compute :as compute]))
(:use (clojure.contrib def core)) (:use (clojure.contrib def core))
(:import org.jclouds.aws.domain.Region (:import org.jclouds.aws.domain.Region
org.jclouds.aws.ec2.domain.AvailabilityZone org.jclouds.compute.domain.NodeMetadata
(org.jclouds.aws.ec2.domain Volume Snapshot AvailabilityZone)
(org.jclouds.aws.ec2.options DescribeSnapshotsOptions DetachVolumeOptions CreateSnapshotOptions))) (org.jclouds.aws.ec2.options DescribeSnapshotsOptions DetachVolumeOptions CreateSnapshotOptions)))
(defn #^org.jclouds.aws.ec2.services.ElasticBlockStoreClient (defn #^org.jclouds.aws.ec2.services.ElasticBlockStoreClient
@ -34,7 +35,7 @@
(-> (or compute compute/*compute*) (-> (or compute compute/*compute*)
.getContext .getProviderSpecificContext .getApi .getElasticBlockStoreServices)) .getContext .getProviderSpecificContext .getApi .getElasticBlockStoreServices))
(defn as-region (defn get-region
"Returns the first argument as the corresponding Region if it is a "Returns the first argument as the corresponding Region if it is a
keyword or already a Region instance. An optional second argument keyword or already a Region instance. An optional second argument
is returned if the first cannot be coerced into a Region. is returned if the first cannot be coerced into a Region.
@ -43,8 +44,23 @@
(cond (cond
(keyword? v) (Region/fromValue (name v)) (keyword? v) (Region/fromValue (name v))
(instance? Region v) v (instance? Region v) v
(instance? NodeMetadata v) (let [zone (.getLocationId v)]
; no easier way to go from zone -> region?
(Region/fromValue (if (> (.indexOf zone "-") -1)
(subs zone 0 (-> zone count dec))
zone)))
:else default-region)) :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 describe-volumes (defn describe-volumes
"Returns a set of org.jclouds.aws.ec2.domain.Volume instances corresponding to the "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). volumes in the specified region (defaulting to your account's default region).
@ -54,50 +70,45 @@
[& [region & volume-ids]] [& [region & volume-ids]]
(set (set
(.describeVolumesInRegion (ebs-services) (.describeVolumesInRegion (ebs-services)
(as-region region Region/DEFAULT) (get-region region Region/DEFAULT)
(into-array String (if (as-region region) (into-array String (map get-volume-id
(if (get-region region)
volume-ids volume-ids
(cons region volume-ids)))))) (when region (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 (defn- snapshot-options
[options-seq] [optmap]
(let [optmap (apply hash-map options-seq) (let [string-array #(let [v (% optmap)]
string-array #(let [v (% optmap)] (into-array String (cond
(into-array String (if (string? v) [v] v)))] (keyword? v) [(name v)]
(string? v) [v]
:else (map as-string v))))]
(-> (DescribeSnapshotsOptions.) (-> (DescribeSnapshotsOptions.)
(.ownedBy (string-array :owner)) (.ownedBy (string-array :owner))
(.snapshotIds (string-array :ids)) (.snapshotIds (string-array :ids))
(.restorableBy (string-array :restorable-by))))) (.restorableBy (string-array :restorable-by)))))
(defn describe-snapshots (defn describe-snapshots
"Returns a set of org.jclouds.aws.ec2.domain.Snapshot instances corresponding to the "Returns a set of org.jclouds.aws.ec2.domain.Snapshot instances that match
snapshots in the specified region (defaulting to your account's default region) the criteria provided. Options include:
limited according to the further options provided.
:region - region string or keyword
: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.
This returns all of the snapshots you own (you can also provide \"amazon\" or an AWS
account ID here):
(with-compute-service [compute] (with-compute-service [compute]
(describe-snapshots [:owner \"self\"])) (describe-snapshots :owner \"self\")
(describe-snapshots :region :us-west-1 :ids [\"snap-44b3ab2d\" \"snap-9e8821f7\"]))"
This returns metadata on the two specified snapshots in us-west-1 (assuming you have access to them): [& options]
(with-compute-service [compute] (let [options (apply hash-map options)
(describe-snapshots :us-west-1 [:ids [\"snap-44b3ab2d\" \"snap-9e8821f7\"]])) region (:region options)
options (snapshot-options (dissoc options :region))]
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 (set
(.describeSnapshotsInRegion (ebs-services) (.describeSnapshotsInRegion (ebs-services)
(as-region region Region/DEFAULT) (get-region region Region/DEFAULT)
(into-array DescribeSnapshotsOptions [options]))))) (into-array DescribeSnapshotsOptions [options])))))
(defn- as-string (defn- as-string
@ -123,14 +134,17 @@
org.jclouds.aws.ec2.domain.Snapshot object representing the created snapshot. org.jclouds.aws.ec2.domain.Snapshot object representing the created snapshot.
e.g. (with-compute-service [compute] e.g. (with-compute-service [compute]
(create-snapshot :us-east-1 \"vol-1dbe6785\") (create-snapshot some-volume-instance)
(create-snapshot :us-east-1 \"vol-1dbe6785\" nil)
(create-snapshot :us-east-1 \"vol-1dbe6785\" \"super-important data\"))" (create-snapshot :us-east-1 \"vol-1dbe6785\" \"super-important data\"))"
[region volume-id & [description]] ([#^Volume volume] (create-snapshot volume nil))
([#^Volume volume description] (create-snapshot (.getRegion volume) (.getId volume) description))
([region volume-id description]
(.createSnapshotInRegion (ebs-services) (.createSnapshotInRegion (ebs-services)
(as-region region) (get-region region)
(as-string volume-id) (as-string volume-id)
(into-array CreateSnapshotOptions (when description (into-array CreateSnapshotOptions (when description
[(.withDescription (CreateSnapshotOptions.) description)])))) [(.withDescription (CreateSnapshotOptions.) description)])))))
(defn delete-snapshot (defn delete-snapshot
"Deletes a snapshot in the specified region. "Deletes a snapshot in the specified region.
@ -138,19 +152,83 @@
e.g. (with-compute-service [compute] e.g. (with-compute-service [compute]
(delete-snapshot :us-east-1 :snap-252310af) (delete-snapshot :us-east-1 :snap-252310af)
(delete-snapshot :us-east-1 \"snap-242adf03\"))" (delete-snapshot :us-east-1 \"snap-242adf03\"))"
[region snapshot-id] ([#^Snapshot snapshot] (delete-snapshot (.getRegion snapshot) (.getId snapshot)))
([region snapshot-id]
(.deleteSnapshotInRegion (ebs-services) (.deleteSnapshotInRegion (ebs-services)
(as-region region) (get-region region)
(as-string snapshot-id))) (as-string snapshot-id))))
(defn get-zone
[v]
(cond
(instance? AvailabilityZone v) v
(instance? NodeMetadata v) (AvailabilityZone/fromValue (.getLocationId #^NodeMetadata v))
(string? v) (AvailabilityZone/fromValue v)
(keyword? v) (AvailabilityZone/fromValue (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.
e.g. (with-compute-service [compute]
(attach-volume :us-east-1 \"i-a92358c1\" :vol-45228a6d \"/dev/sdh\")
(attach-volume some-node-instance :vol-45228a6d \"/dev/sdh\")
(attach-volume some-node-instance some-volume-instance \"/dev/sdh\"))"
([#^NodeMetadata node volume device]
(attach-volume node (.getId node) (get-volume-id volume) device))
([region instance-id volume-id device]
(apply #(.attachVolumeInRegion (ebs-services)
(get-region region) % %2 %3)
(map as-string [volume-id instance-id device]))))
(defn detach-volume
"Detatches 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."
[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-services)
region
volume-id
(boolean (:force options))
(into-array DetachVolumeOptions []))))
(defn create-volume (defn create-volume
"Creates a new volume given a set of options. :zone is required, one "Creates a new volume given a set of options:
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. - one of :zone (keyword, string, or AvailabilityZone) or :node (NodeMetadata)
Returns the created org.jclouds.aws.ec2.domain.Volume. - one or both of :snapshot (keyword or string) or :size (string, keyword, or number)
- only if also attaching the new volume: :attach (logical boolean) and :device (string or keyword)
Returns a vector of [created org.jclouds.aws.ec2.domain.Volume,
optional org.jclouds.aws.ec2.domain.Attachment]
Note that specifying :node instead of :zone will only attach the created volume
if :attach is logically true, and :device is provided. Otherwise, the node is only
used to obtain the desired availability zone.
e.g. (with-compute-service [compute] e.g. (with-compute-service [compute]
(create-volume :zone :us-east-1a :size 250) (create-volume :zone :us-east-1a :size 250)
(create-volume :node node-instance :size 250)
(create-volume :node node-instance :size 250 :attach true :device \"/dev/sdj\")
(create-volume :zone :eu-west-1b :snapshot \"snap-252310af\") (create-volume :zone :eu-west-1b :snapshot \"snap-252310af\")
(create-volume :zone :eu-west-1b :snapshot \"snap-252310af\" :size :1024))" (create-volume :zone :eu-west-1b :snapshot \"snap-252310af\" :size :1024))"
[& options] [& options]
@ -159,18 +237,21 @@
(let [options (apply hash-map options) (let [options (apply hash-map options)
snapshot (get-string options :snapshot) snapshot (get-string options :snapshot)
size (-?> (get-string options :size) as-int) size (-?> (get-string options :size) as-int)
zone (get-string options :zone) #^NodeMetadata node (:node options)
zone (or node (get-string options :zone))
zone (if zone zone (if zone
(if (instance? AvailabilityZone zone) (get-zone zone)
zone (throw (IllegalArgumentException. "Must supply a :zone or :node option.")))
(AvailabilityZone/fromValue zone))
(throw (IllegalArgumentException. "Must supply a :zone option.")))
ebs (ebs-services)] ebs (ebs-services)]
(cond (when (and (:attach options) (or (not node) (not (:device options))))
(throw (IllegalArgumentException. "Cannot create and attach new volume; no :node and/or :device specified")))
(let [new-volume (cond
(and snapshot size) (.createVolumeFromSnapshotInAvailabilityZone ebs zone size snapshot) (and snapshot size) (.createVolumeFromSnapshotInAvailabilityZone ebs zone size snapshot)
snapshot (.createVolumeFromSnapshotInAvailabilityZone ebs zone snapshot) snapshot (.createVolumeFromSnapshotInAvailabilityZone ebs zone snapshot)
size (.createVolumeInAvailabilityZone ebs zone size) size (.createVolumeInAvailabilityZone ebs zone size)
:else (throw (IllegalArgumentException. "Must supply :size and/or :snapshot options."))))) :else (throw (IllegalArgumentException. "Must supply :size and/or :snapshot options.")))]
[new-volume (when (:attach options)
(attach-volume node new-volume (:device options)))])))
(defn delete-volume (defn delete-volume
"Deletes a volume in the specified region. "Deletes a volume in the specified region.
@ -178,33 +259,9 @@
e.g. (with-compute-service [compute] e.g. (with-compute-service [compute]
(delete-volume :us-east-1 :vol-45228a6d) (delete-volume :us-east-1 :vol-45228a6d)
(delete-volume :us-east-1 \"vol-052b846c\"))" (delete-volume :us-east-1 \"vol-052b846c\"))"
[region volume-id] ([#^Volume volume]
(delete-volume (.getRegion volume) (.getId volume)))
([region volume-id]
(.deleteVolumeInRegion (ebs-services) (.deleteVolumeInRegion (ebs-services)
(as-region region) (get-region region)
(as-string volume-id))) (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 [])))