add nodes#create and nodes#ping
authorTom Clegg <tom@clinicalfuture.com>
Tue, 8 Jan 2013 08:59:39 +0000 (00:59 -0800)
committerTom Clegg <tom@clinicalfuture.com>
Tue, 8 Jan 2013 08:59:39 +0000 (00:59 -0800)
14 files changed:
app/assets/javascripts/nodes.js [new file with mode: 0644]
app/assets/stylesheets/nodes.css [new file with mode: 0644]
app/controllers/application_controller.rb
app/controllers/orvos/v1/nodes_controller.rb [new file with mode: 0644]
app/helpers/nodes_helper.rb [new file with mode: 0644]
app/models/node.rb [new file with mode: 0644]
config/routes.rb
db/migrate/20130107212832_create_nodes.rb [new file with mode: 0644]
db/schema.rb
public/discovery/v1/apis/orvos/v1/rest
test/fixtures/nodes.yml [new file with mode: 0644]
test/functional/nodes_controller_test.rb [new file with mode: 0644]
test/unit/helpers/nodes_helper_test.rb [new file with mode: 0644]
test/unit/node_test.rb [new file with mode: 0644]

diff --git a/app/assets/javascripts/nodes.js b/app/assets/javascripts/nodes.js
new file mode 100644 (file)
index 0000000..dee720f
--- /dev/null
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/stylesheets/nodes.css b/app/assets/stylesheets/nodes.css
new file mode 100644 (file)
index 0000000..afad32d
--- /dev/null
@@ -0,0 +1,4 @@
+/*
+  Place all the styles related to the matching controller here.
+  They will automatically be included in application.css.
+*/
index 15f314ce46f9523ff3fe1ef254709d07e3cfdf3f..9c29f16536071fca32766135935aaf17b2d67a94 100644 (file)
@@ -1,9 +1,19 @@
 class ApplicationController < ActionController::Base
   protect_from_forgery
   before_filter :uncamelcase_params_hash_keys
+  before_filter :find_object_by_uuid
 
   protected
 
+  def model_class
+    controller_name.classify.constantize
+  end
+
+  def find_object_by_uuid
+    logger.info params.inspect
+    @object = model_class.where('uuid=?', params[:uuid]).first
+  end
+
   def uncamelcase_params_hash_keys
     uncamelcase_hash_keys(params)
   end
diff --git a/app/controllers/orvos/v1/nodes_controller.rb b/app/controllers/orvos/v1/nodes_controller.rb
new file mode 100644 (file)
index 0000000..61f40d7
--- /dev/null
@@ -0,0 +1,19 @@
+class Orvos::V1::NodesController < ApplicationController
+  def create
+    @object = Node.new
+    @object.save!
+    @object.start!(lambda { |h| orvos_v1_ping_node_url(h) })
+    show
+  end
+
+  def show
+    render json: @object.to_json
+  end
+
+  def ping
+    @object.ping({ ip: request.env['REMOTE_ADDR'],
+                   ping_secret: params[:ping_secret],
+                   ec2_instance_id: params[:ec2_instance_id] })
+    show
+  end
+end
diff --git a/app/helpers/nodes_helper.rb b/app/helpers/nodes_helper.rb
new file mode 100644 (file)
index 0000000..673b561
--- /dev/null
@@ -0,0 +1,2 @@
+module NodesHelper
+end
diff --git a/app/models/node.rb b/app/models/node.rb
new file mode 100644 (file)
index 0000000..6f73d4a
--- /dev/null
@@ -0,0 +1,70 @@
+class Node < ActiveRecord::Base
+  include AssignUuid
+  serialize :info, Hash
+  before_validation :ensure_ping_secret
+
+  MAX_SLOTS = 64
+
+  def info
+    @info ||= Hash.new
+    super
+  end
+
+  def ping(o)
+    raise "must have :ip and :ping_secret" unless o[:ip] and o[:ping_secret]
+
+    if o[:ping_secret] != self.info[:ping_secret]
+      logger.info "Ping: secret mismatch: received \"#{o[:ping_secret]}\" != \"#{self.info[:ping_secret]}\""
+      return nil
+    end
+    self.last_ping_at = Time.now
+
+    # Record IP address
+    if self.ip_address.nil?
+      logger.info "#{self.uuid} ip_address= #{o[:ip]}"
+      self.ip_address = o[:ip]
+      self.first_ping_at = Time.now
+    end
+
+    # Record instance ID if not already known
+    self.info[:ec2_instance_id] ||= o[:ec2_instance_id]
+
+    # Assign hostname
+    if self.slot_number.nil?
+      try_slot = 0
+      begin
+        self.slot_number = try_slot
+        try_slot += 1
+        break if self.save rescue nil
+        raise "No available node slots" if try_slot == MAX_SLOTS
+      end while true
+      self.hostname = "compute#{self.slot_number}"
+    end
+
+    save
+  end
+
+  def start!(ping_url_method)
+    ping_url = ping_url_method.call({ uuid: self.uuid, ping_secret: self.info[:ping_secret] })
+    cmd = ["ec2-run-instances",
+           "--user-data '#{ping_url}'",
+           "-t c1.xlarge -n 1 -g orvos-compute",
+           "ami-68ca6901"
+          ].join(' ')
+    self.info[:ec2_start_command] = cmd
+    logger.info "#{self.uuid} ec2_start_command= #{cmd.inspect}"
+    result = `#{cmd} 2>&1`
+    self.info[:ec2_start_result] = result
+    logger.info "#{self.uuid} ec2_start_result= #{result.inspect}"
+    result.match(/INSTANCE\s*(i-[0-9a-f]+)/) do |m|
+      self.info[:ec2_instance_id] = m[1]
+      self.save!
+    end
+  end
+
+  protected
+
+  def ensure_ping_secret
+    self.info[:ping_secret] ||= rand(2**256).to_s(36)
+  end
+end
index 59c1e6e4584fe16279eeb97d4223008315c381e4..0cfeac851bf814a32516d2ccbf40cb914b3c90a9 100644 (file)
@@ -1,4 +1,5 @@
 Server::Application.routes.draw do
+  resources :nodes
   resources :collections
   resources :metadata
 
@@ -63,6 +64,8 @@ Server::Application.routes.draw do
     namespace :v1 do
       resources :collections
       resources :metadata
+      resources :nodes
+      match '/nodes/:uuid/ping' => 'nodes#ping', :as => :ping_node
     end
   end
 
diff --git a/db/migrate/20130107212832_create_nodes.rb b/db/migrate/20130107212832_create_nodes.rb
new file mode 100644 (file)
index 0000000..6ca977a
--- /dev/null
@@ -0,0 +1,28 @@
+class CreateNodes < ActiveRecord::Migration
+  def up
+    create_table :nodes do |t|
+      t.string :uuid
+      t.string :created_by_client
+      t.string :created_by_user
+      t.datetime :created_at
+      t.string :modified_by_client
+      t.string :modified_by_user
+      t.datetime :modified_at
+      t.integer :slot_number
+      t.string :hostname
+      t.string :domain
+      t.string :ip_address
+      t.datetime :first_ping_at
+      t.datetime :last_ping_at
+      t.text :info
+
+      t.timestamps
+    end
+    add_index :nodes, :uuid, :unique => true
+    add_index :nodes, :slot_number, :unique => true
+    add_index :nodes, :hostname, :unique => true
+  end
+  def down
+    drop_table :nodes
+  end
+end
index 4557611b28f8386f6a5dc6b1eecd7a6750f05df6..5db586226479d3851de1ff5666363ff162b69d0c 100644 (file)
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20130107181109) do
+ActiveRecord::Schema.define(:version => 20130107212832) do
 
   create_table "collections", :force => true do |t|
     t.string   "locator"
@@ -50,4 +50,26 @@ ActiveRecord::Schema.define(:version => 20130107181109) do
     t.datetime "updated_at"
   end
 
+  create_table "nodes", :force => true do |t|
+    t.string   "uuid"
+    t.string   "created_by_client"
+    t.string   "created_by_user"
+    t.datetime "created_at"
+    t.string   "modified_by_client"
+    t.string   "modified_by_user"
+    t.datetime "modified_at"
+    t.integer  "slot_number"
+    t.string   "hostname"
+    t.string   "domain"
+    t.string   "ip_address"
+    t.datetime "first_ping_at"
+    t.datetime "last_ping_at"
+    t.text     "info"
+    t.datetime "updated_at"
+  end
+
+  add_index "nodes", ["hostname"], :name => "index_nodes_on_hostname", :unique => true
+  add_index "nodes", ["slot_number"], :name => "index_nodes_on_slot_number", :unique => true
+  add_index "nodes", ["uuid"], :name => "index_nodes_on_uuid", :unique => true
+
 end
index a08bb37bcfa0927f39518b641d20db0fa7df6c30..8380ff3fbc6d465a32a77f624a852b478bc3aa60 100644 (file)
     }
    }
   },
+  "nodes": {
+   "methods": {
+     "create": {
+      "id": "orvos.nodes.create",
+      "path": "nodes",
+      "httpMethod": "POST",
+      "parameters": {
+      }
+     },
+     "get": {
+      "id": "orvos.nodes.get",
+      "path": "nodes/{nodeId}",
+      "httpMethod": "GET",
+      "parameters": {
+       "uuid": {
+       "type": "string"
+       }
+      }
+     }
+   }
+  },
   "collections": {
    "methods": {
     "copy": {
diff --git a/test/fixtures/nodes.yml b/test/fixtures/nodes.yml
new file mode 100644 (file)
index 0000000..c63aac0
--- /dev/null
@@ -0,0 +1,11 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
+
+# This model initially had no columns defined.  If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+#  column: value
diff --git a/test/functional/nodes_controller_test.rb b/test/functional/nodes_controller_test.rb
new file mode 100644 (file)
index 0000000..778c092
--- /dev/null
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class NodesControllerTest < ActionController::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
diff --git a/test/unit/helpers/nodes_helper_test.rb b/test/unit/helpers/nodes_helper_test.rb
new file mode 100644 (file)
index 0000000..13011de
--- /dev/null
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class NodesHelperTest < ActionView::TestCase
+end
diff --git a/test/unit/node_test.rb b/test/unit/node_test.rb
new file mode 100644 (file)
index 0000000..ccc3765
--- /dev/null
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class NodeTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end