 age_h = (time) -> (new Date() - time)/(3600*1000)
 age_s = (time) -> (new Date() - time)/1000
 
-json_output_cb = (cb) ->
-    (err, output) ->
-        if not err
-            cb(undefined, misc.from_json(output.stdout))
-        else
-            cb(err)
-
-class Instance
+onCompleteOpts =
+    maxAttempts : 30
+
+handle_operation = (err, operation, done, cb) ->
+    if err
+        done()
+        cb?(err)
+    else
+        operation.onComplete onCompleteOpts, (err, metadata) ->
+            done()
+            if err
+                cb?(err)
+            else if metadata.error
+                cb?(metadata.error)
+            else
+                cb?()
+
+class VM
     constructor: (@gcloud, @name, @zone=DEFAULT_ZONE) ->
-
-    dbg: (f) -> @gcloud("instance.#{f}")
-
-    _command : (opts) =>
-        opts = defaults opts,
-            category : 'instances'
-            action   : required   # 'start', 'stop', 'reset', etc.
-            args     : []
-            cb       : undefined
-        @gcloud._command(misc.merge(opts, {name:@name, zone:@zone}))
+        @_vm = @gcloud._gce.zone(@zone).vm(@name)
+
+    dbg: (f) -> @gcloud.dbg("vm.#{f}")
+
+    _action: (cmd, cb) =>
+        dbg = @dbg(cmd)
+        dbg('calling api...')
+        start = misc.walltime()
+        @_vm[cmd] (err, operation, apiResponse) ->
+            handle_operation(err, operation, (->dbg("done -- took #{misc.walltime(start)}s")), cb)
 
     stop : (opts={}) =>
-        @_command(misc.merge(opts, {action:'stop'}))
+        @_action('stop', opts.cb)
 
     start: (opts={}) =>
-        @_command(misc.merge(opts, {action:'start'}))
+        @_action('start', opts.cb)
 
     reset: (opts={}) =>
-        @_command(misc.merge(opts, {action:'reset'}))
-
-    describe: (opts) =>
-        opts.cb = json_output_cb(opts.cb)
-        @_command(misc.merge(opts, {action:'describe', args:['--format', 'json']}))
+        @_action('reset', opts.cb)
+
+    get_metadata: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("metadata")
+        dbg("starting")
+        @_vm.getMetadata (err, metadata, apiResponse) =>
+            dbg("done")
+            opts.cb(err, metadata)
+
+    status : (opts) =>
+        opts = defaults opts,
+            cb : required
+        @describe
+            cb : (err, x) =>
+                opts.cb(err, x?.status)
 
 class Disk
     constructor: (@gcloud, @name, @zone=DEFAULT_ZONE) ->
-
-    dbg: (f) -> @gcloud("disk.#{f}")
-
-    _command : (opts) =>
-        opts = defaults opts,
-            category : 'disks'
-            action   : required
-            args     : []
-            cb       : undefined
-        @gcloud._command(misc.merge(opts, {name:@name, zone:@zone}))
-
-    snapshot : (opts) =>
+        @_disk = @gcloud._gce.zone(@zone).disk(@name)
+
+    dbg: (f) -> @gcloud.dbg("disk.#{f}")
+
+    #createSnapshot, delete, getMetadata
+
+    snapshot: (opts) =>
         opts = defaults opts,
             name : required
             cb   : undefined
-        @_command
-            action : 'snapshot'
-            args   : ['--snapshot-names', opts.name]
+        dbg = @dbg('snapshot')
+        dbg('calling api')
+        start = misc.walltime()
+        done = -> dbg("done  -- took #{misc.walltime(start)}s")
+        @_disk.createSnapshot opts.name, (err, snapshot, operation, apiResponse) =>
+            handle_operation(err, operation, done, opts.cb)
+
+    get_metadata: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("metadata")
+        dbg("starting")
+        @_disk.getMetadata (err, metadata, apiResponse) =>
+            dbg("done")
+            opts.cb(err, metadata)
+
+    # return the snapshots of this disk
+    get_snapshots: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("get_snapshots")
+        id = undefined
+        s = undefined
+        async.series([
+            (cb) =>
+                dbg("determining id of disk")
+                @get_metadata
+                    cb : (err, data) =>
+                        id = data?.id; cb(err)
+            (cb) =>
+                dbg("get all snapshots with given id as source")
+                @gcloud._gce.getSnapshots {filter:"sourceDiskId eq #{id}", maxResults:500}, (err, snapshots) =>
+                    if err
+                        cb(err)
+                    else
+                        s = (@gcloud.snapshot(name:x.name) for x in snapshots)
+                        cb()
+        ], (err) =>
+            opts.cb(err, s)
+        )
+
+    delete: (opts) =>
+        opts = defaults opts,
+            cb : undefined
+        dbg = @dbg("delete")
+        dbg("starting")
+        @_disk.delete (err, operation, apiResponse) =>
+            handle_operation(err, operation, (->dbg('done')), opts.cb)
 
 class Snapshot
     constructor: (@gcloud, @name) ->
-
-    dbg: (f) -> @gcloud("snapshot.#{f}")
-
-    describe: (opts) =>
-        opts = defaults opts,
-            cb : required
-        @gcloud._gcloud
-            args : ['snapshots', 'describe', @name, '--format', json]
-            cb   : json_output_cb(opts.cb)
-
+        @_snapshot = @gcloud._gce.snapshot(@name)
+
+    dbg: (f) -> @gcloud.dbg("snapshot.#{f}")
+
+    delete: (opts) =>
+        opts = defaults opts,
+            cb : undefined
+        dbg = @dbg("delete")
+        dbg("starting")
+        @_snapshot.delete (err, operation, apiResponse) =>
+            handle_operation(err, operation, (->dbg('done')), opts.cb)
+
+    get_metadata: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @dbg("metadata")
+        dbg("starting")
+        @_snapshot.getMetadata (err, metadata, apiResponse) =>
+            dbg("done")
+            opts.cb(err, metadata)
+
+    get_size_GB: (opts) =>
+        opts = defaults opts,
+            cb : required
+        if @_snapshot.metadata.storageBytes
+            opts.cb(undefined, @_snapshot.metadata.storageBytes / 1000 / 1000 / 1000)
+        else
+            @get_metadata
+                cb : (err, data) =>
+                    if err
+                        opts.cb(err)
+                    else
+                        opts.cb(undefined, data.storageBytes / 1000 / 1000 / 1000)
 
 class GoogleCloud
     constructor: (opts={}) ->

###
Rewrite this to use the official node.js driver, which is pretty good now, and seems an order
of magnitude faster than using the gcloud command line!

https://googlecloudplatform.github.io/gcloud-node/#/docs/v0.23.0/compute/snapshot
https://github.com/GoogleCloudPlatform/gcloud-node

npm install --save gcloud
gcloud = require('gcloud')(projectId: 'sage-math-inc')
gce=gcloud.compute()
snapshot = gce.snapshot('test')
snapshot.getMetadata((err, metadata, apiResponse) -> console.log(err, metadata, apiResponse))
zone = gce.zone('us-central1-c')
vm = zone.vm('test')
vm.getMetadata((err, metadata, apiResponse) -> console.log(err, metadata, apiResponse))
vm.stop((err, op, apiResponse) -> console.log(err, op, apiResponse);global.op=op)
op.onComplete(->console.log("DONE"))
###



###
For now the only rules we care about are:

1. If an instance is TERMINATED, but the desired state is RUNNING and preempt is true, then:
     if it was TERMINATED within 5 minutes create it as non-preempt and start it
     if it was TERMINATED > 5 minutes ago create it as preempt and start it.

2. If an instance has been RUNNING for 12 hours and is not preempt, but the desired state
   is RUNNING and preempt is true, then:
     stop the instance and recreate and start it as prempt.

###
winston     = require('winston')

        else
            @dbg = (f) -> (->)

        @_gcloud = require('gcloud')(projectId: PROJECT)
        @_gce    = @_gcloud.compute()

    vm: (opts) =>
        opts = defaults opts,
            name : required
            zone : DEFAULT_ZONE
        key = "#{opts.name}-#{opts.zone}"
        # create cache if not already created
        @_vm_cache ?= {}
        # set value for key if not already set; return it
        return (@_vm_cache[key] ?= new VM(@, opts.name, opts.zone))

    disk: (opts) =>
        opts = defaults opts,
