--- /dev/null
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
--- /dev/null
+/*
+ Place all the styles related to the matching controller here.
+ They will automatically be included in application.css.
+*/
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
--- /dev/null
+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
--- /dev/null
+module NodesHelper
+end
--- /dev/null
+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
Server::Application.routes.draw do
+ resources :nodes
resources :collections
resources :metadata
namespace :v1 do
resources :collections
resources :metadata
+ resources :nodes
+ match '/nodes/:uuid/ping' => 'nodes#ping', :as => :ping_node
end
end
--- /dev/null
+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
#
# 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"
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
}
}
},
+ "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": {
--- /dev/null
+# 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
--- /dev/null
+require 'test_helper'
+
+class NodesControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
--- /dev/null
+require 'test_helper'
+
+class NodesHelperTest < ActionView::TestCase
+end
--- /dev/null
+require 'test_helper'
+
+class NodeTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end