You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by mf...@redhat.com on 2010/11/08 12:38:21 UTC

Support for load_balancer collection

Hi,

I found some unpushed load_balancer patch from Aug/2010, so I revamped
it to work with latest core.

So what are basicly load balancers ? It's simple, you create an load_balancer
object, set port for this object and then set port for instance objects.
After you create load_balancer, you can assign multiple instances into this
object and all traffic will be routed through this load balancer.

There are more articles about Amazon Elastic Load Balancing:
http://aws.amazon.com/elasticloadbalancing/

GoGrid also offer their F5 load balancers, more reading here:
http://wiki.gogrid.com/wiki/index.php/API:Load_Balancer_%28Object%29
http://wiki.gogrid.com/wiki/index.php/Network_Devices#f5

 -- Michal



[PATCH core 2/4] Added support for load_balancer collection

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

---
 server/lib/deltacloud/base_driver/features.rb      |    8 ++
 .../lib/deltacloud/helpers/application_helper.rb   |    4 +-
 server/public/javascripts/application.js           |   12 +++
 server/server.rb                                   |   76 ++++++++++++++++++++
 server/views/instances/new.html.haml               |    8 ++
 server/views/load_balancers/index.html.haml        |   33 +++++++++
 server/views/load_balancers/index.xml.haml         |    5 ++
 server/views/load_balancers/new.html.haml          |   38 ++++++++++
 server/views/load_balancers/show.html.haml         |   37 ++++++++++
 server/views/load_balancers/show.xml.haml          |   21 ++++++
 10 files changed, 240 insertions(+), 2 deletions(-)
 create mode 100644 server/views/load_balancers/index.html.haml
 create mode 100644 server/views/load_balancers/index.xml.haml
 create mode 100644 server/views/load_balancers/new.html.haml
 create mode 100644 server/views/load_balancers/show.html.haml
 create mode 100644 server/views/load_balancers/show.xml.haml

diff --git a/server/lib/deltacloud/base_driver/features.rb b/server/lib/deltacloud/base_driver/features.rb
index 3b19ca2..2d79f4b 100644
--- a/server/lib/deltacloud/base_driver/features.rb
+++ b/server/lib/deltacloud/base_driver/features.rb
@@ -169,5 +169,13 @@ module Deltacloud
         param :location, :string, :optional
       end
     end
+
+    declare_feature :instances, :register_to_load_balancer do
+      description "Register instance to load balancer"
+      operation :create do
+        param :load_balancer_id, :string, :optional
+      end
+    end
+
   end
 end
diff --git a/server/lib/deltacloud/helpers/application_helper.rb b/server/lib/deltacloud/helpers/application_helper.rb
index 189148c..00e8bc9 100644
--- a/server/lib/deltacloud/helpers/application_helper.rb
+++ b/server/lib/deltacloud/helpers/application_helper.rb
@@ -41,8 +41,8 @@ module ApplicationHelper
     collections[:instances].operations[action.to_sym].method
   end
 
-  def driver_has_feature?(feature_name)
-    not driver.features(:instances).select{ |f| f.name.eql?(feature_name) }.empty?
+  def driver_has_feature?(feature_name, collection_name = :instances)
+    not driver.features(collection_name).select{ |f| f.name.eql?(feature_name) }.empty?
   end
 
   def driver_has_auth_features?
diff --git a/server/public/javascripts/application.js b/server/public/javascripts/application.js
index 80e1d1c..509768b 100644
--- a/server/public/javascripts/application.js
+++ b/server/public/javascripts/application.js
@@ -29,4 +29,16 @@ $(document).ready(function() {
     return false;
   })
 
+  if ($('select#list_instances').length) {
+    $('select#list_instances').html("<option>Loading instances...</option>");
+    $.getJSON("/api/instances?state=RUNNING&format=json",
+      function(data){
+        $('select#list_instances').empty();
+        $.each(data.instances, function(i,item){
+          $('select#list_instances').append('<option value="'+item.id+'">'+item.id+'</option>');
+        });
+      }
+    );
+  }
+
 })
diff --git a/server/server.rb b/server/server.rb
index b8720a9..7044cd1 100644
--- a/server/server.rb
+++ b/server/server.rb
@@ -175,11 +175,87 @@ get "/api/instances/new" do
   @image   = driver.image( credentials, :id => params[:image_id] )
   @hardware_profiles = driver.hardware_profiles(credentials, :architecture => @image.architecture )
   @realms = driver.realms(credentials)
+  if driver_has_feature?(:register_to_load_balancer)
+    @load_balancers = driver.load_balancers(credentials)
+  end
   respond_to do |format|
     format.html { haml :"instances/new" }
   end
 end
 
+get '/api/load_balancers/new' do
+  @realms = driver.realms(credentials)
+  @instances = driver.instances(credentials) if driver_has_feature?(:register_instance, :load_balancers)
+  respond_to do |format|
+    format.html { haml :"load_balancers/new" }
+  end
+end
+
+
+collection :load_balancers do
+  description "Load balancers"
+
+  operation :index do
+    description "List of all active load balancers"
+    control do
+      filter_all :load_balancers
+    end
+  end
+
+  operation :show do
+    description "Show details about given load balancer"
+    param :id,  :string,  :required
+    control { show :load_balancer }
+  end
+
+  operation :create do
+    description "Create a new load balancer"
+    param :name,  :string,  :required
+    param :realm_id,  :string,  :required
+    param :listener_protocol,  :string,  :required, ['HTTP', 'TCP']
+    param :listener_lbr_port,  :string,  :required
+    param :listener_inst_port,  :string,  :required
+    control do
+      @load_balancer = driver.create_load_balancer(credentials, params)
+      respond_to do |format|
+        format.xml { haml :"load_balancers/show" }
+        format.html { haml :"load_balancers/show" }
+      end
+    end
+  end
+
+  operation :register, :method => :post, :member => true do
+    description "Add instance to loadbalancer"
+    param :id,  :string,  :required
+    param :instance_id, :string,  :required
+    control do
+      driver.lb_register_instance(credentials, params)
+      redirect(load_balancer_url(params[:id]))
+    end
+  end
+
+  operation :unregister, :method => :post, :member => true do
+    description "Remove instance from loadbalancer"
+    param :id,  :string,  :required
+    param :instance_id, :string,  :required
+    control do
+      driver.lb_unregister_instance(credentials, params)
+      redirect(load_balancer_url(params[:id]))
+    end
+  end
+
+  operation :destroy do
+    description "Destroy given load balancer"
+    param :id,  :string,  :required
+    control do
+      driver.destroy_load_balancer(credentials, params[:id])
+      redirect(load_balancers_url)
+    end
+  end
+
+end
+
+
 collection :instances do
   description <<END
   An instance is a concrete machine realized from an image.
diff --git a/server/views/instances/new.html.haml b/server/views/instances/new.html.haml
index 6d67c2d..558582d 100644
--- a/server/views/instances/new.html.haml
+++ b/server/views/instances/new.html.haml
@@ -9,6 +9,14 @@
     %label
       Instance Name:
       %input{ :name => 'name', :size => 30 }/
+  -if driver_has_feature?(:register_to_load_balancer)
+    %p
+      %label
+        Assign to load balancer:
+        %select{:name => 'load_balancer_id'}
+          %option{:value => ""}
+          - @load_balancers.each do |load_balancer|
+            %option{:value => load_balancer.id} #{load_balancer.id}
   -if driver_has_feature?(:authentication_key)
     %p
       %label
diff --git a/server/views/load_balancers/index.html.haml b/server/views/load_balancers/index.html.haml
new file mode 100644
index 0000000..65b109c
--- /dev/null
+++ b/server/views/load_balancers/index.html.haml
@@ -0,0 +1,33 @@
+%h1 Load Balancers
+
+%table.display
+  %thead
+    %tr
+      %th ID
+      %th Hostname
+      %th Realm
+      %th Balancer port
+      %th Instances port
+      %th Actions
+  %tbody
+    - @elements.each do |balancer|
+      %tr
+        %td
+          = link_to balancer.id, load_balancer_url( balancer.id )
+        %td
+          = balancer.public_addresses.first
+        %td
+          = link_to balancer.realms.first.id, realm_url( balancer.realms.first.id )
+        %td
+          - balancer.listeners.each do |listener|
+            ="#{listener.protocol}[#{listener.load_balancer_port}]<br/>"
+        %td
+          - balancer.listeners.each do |listener|
+            ="#{listener.protocol}[#{listener.load_balancer_port}]<br/>"
+        %td
+          =link_to 'Destroy', destroy_load_balancer_url(balancer.id), :class => 'delete'
+  %tfoot
+    %tr
+      %td{:colspan => 6, :style => "text-align:right;"}
+        =link_to 'Create &raquo;', "#{url_for('/api/load_balancers/new')}", :class => 'button'
+
diff --git a/server/views/load_balancers/index.xml.haml b/server/views/load_balancers/index.xml.haml
new file mode 100644
index 0000000..22c6911
--- /dev/null
+++ b/server/views/load_balancers/index.xml.haml
@@ -0,0 +1,5 @@
+
+!!!XML
+%load_balancers
+  - @elements.each do |c|
+    = haml :'load_balancers/show', :locals => { :@load_balancer => c, :partial => true }
diff --git a/server/views/load_balancers/new.html.haml b/server/views/load_balancers/new.html.haml
new file mode 100644
index 0000000..561caa4
--- /dev/null
+++ b/server/views/load_balancers/new.html.haml
@@ -0,0 +1,38 @@
+%h1 New Load Balancer
+
+%form{ :action => '/api/load_balancers', :method => :post }
+  %p
+    %label
+      Name:
+    %input{ :name => 'name', :size => 30 }/
+  -if @instances
+    %p
+      %label
+        Running instance:
+      %select{ :name => 'instance_id'}
+        - @instances.select{|i| i.state=="RUNNING"}.each do |instance|
+          %option{ :value => instance.id } #{instance.id}
+  %p
+    %label
+      Realm:
+    %select{ :name => 'realm_id'}
+      - @realms.each do |realm|
+        %option{ :value => realm.id } #{realm.id} - #{realm.name}
+  %hr
+  %p
+    %label
+      Protocol:
+    %select{ :name => 'listener_protocol'}
+      %option{ :value => 'HTTP'}  HTTP
+      %option{ :value => 'TCP'} TCP
+  %p
+    %label
+      Load balancer port:
+    %input{ :name => "listener_lbr_port", :size => 30}
+  %p
+    %label
+      Instances port:
+    %input{ :name => "listener_inst_port", :size => 30}
+  %p
+    %input{ :type => :submit, :name => "commit", :value => "create" }/
+
diff --git a/server/views/load_balancers/show.html.haml b/server/views/load_balancers/show.html.haml
new file mode 100644
index 0000000..7b6f40b
--- /dev/null
+++ b/server/views/load_balancers/show.html.haml
@@ -0,0 +1,37 @@
+%h1
+  = @load_balancer.id
+
+%dl
+  %di
+    %dt Public addresses
+    %dd
+      = @load_balancer.public_addresses.join(',')
+    - if @load_balancer.created_at
+      %dt Created at
+      %dd
+        = @load_balancer.created_at
+    %dt Realms
+    %dd
+      = @load_balancer.realms.collect { |r| "#{r.id} - #{r.name}" }.join(',')
+    %dt Listeners
+    %dd
+      - @load_balancer.listeners.each do |listener|
+        ="Load balancer port: #{listener.load_balancer_port}"
+        %br
+        ="Instance port: #{listener.instance_port}"
+        %br
+    - if @load_balancer.instances.class.eql?(Array)
+      %dt Instances
+      - @load_balancer.instances.each do |inst|
+        %dd
+          =inst.id
+          %a{:class => :post, :href => unregister_load_balancer_url(@load_balancer.id, :instance_id => inst.id)} Delete
+
+%form{:action => url_for("/api/load_balancers/#{@load_balancer.id}/register"), :method => :post}
+  %p
+    %strong Add instances to load balancer
+  %p
+    %label Instance
+    %select{:name => :instance_id, :id => "list_instances"}
+    %input{:type => :submit, :value => "Assign"}
+
diff --git a/server/views/load_balancers/show.xml.haml b/server/views/load_balancers/show.xml.haml
new file mode 100644
index 0000000..e36f79f
--- /dev/null
+++ b/server/views/load_balancers/show.xml.haml
@@ -0,0 +1,21 @@
+- unless defined?(partial)
+  !!! XML
+%load_balancer{ :href => key_url(@load_balancer.id), :id => @load_balancer.id}
+  %actions
+    %link{ :rel => "destroy", :method => "delete", :href => destroy_load_balancer_url(@load_balancer.id)}
+  %public_addresses
+    - @load_balancer.public_addresses.each do |address|
+      %address #{address}
+  %created_at<
+    = @load_balancer.created_at
+  %realm{ :href => realm_url(@load_balancer.realms.first.id), :id => @load_balancer.realms.first.id}
+  %listeners
+    - @load_balancer.listeners.each do |listener|
+      %listener{ :protocol => listener.protocol}
+        %load_balancer_port #{listener.load_balancer_port}
+        %instance_port #{listener.instance_port}
+  %instances
+    - @load_balancer.instances.each do |instance|
+      %instance{:href => instance_url(instance.id), :id => instance.id}
+        %link{:rel => "unregister", :href => unregister_load_balancer_url(@load_balancer.id, { :instance_id => instance.id})}
+
-- 
1.7.3.2


Re: Support for load_balancer collection

Posted by Michal Fojtik <mf...@redhat.com>.
On 08/11/10 12:38 +0100, mfojtik@redhat.com wrote:
>Hi,
>
>I found some unpushed load_balancer patch from Aug/2010, so I revamped
>it to work with latest core.
>
>So what are basicly load balancers ? It's simple, you create an load_balancer
>object, set port for this object and then set port for instance objects.
>After you create load_balancer, you can assign multiple instances into this
>object and all traffic will be routed through this load balancer.
>
>There are more articles about Amazon Elastic Load Balancing:
>http://aws.amazon.com/elasticloadbalancing/
>
>GoGrid also offer their F5 load balancers, more reading here:
>http://wiki.gogrid.com/wiki/index.php/API:Load_Balancer_%28Object%29
>http://wiki.gogrid.com/wiki/index.php/Network_Devices#f5
>

Damn, send-email  screwed up something. Sending all patches again.

   -- Michal


-- 
--------------------------------------------------------
Michal Fojtik, mfojtik@redhat.com
Deltacloud API: http://deltacloud.org
--------------------------------------------------------

[PATCH core 3/3] Added XML and HTML representation of 405 error code

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

---
 server/views/errors/not_allowed.html.haml |    6 ++++++
 server/views/errors/not_allowed.xml.haml  |    2 ++
 2 files changed, 8 insertions(+), 0 deletions(-)
 create mode 100644 server/views/errors/not_allowed.html.haml
 create mode 100644 server/views/errors/not_allowed.xml.haml

diff --git a/server/views/errors/not_allowed.html.haml b/server/views/errors/not_allowed.html.haml
new file mode 100644
index 0000000..59e9fe9
--- /dev/null
+++ b/server/views/errors/not_allowed.html.haml
@@ -0,0 +1,6 @@
+%h1 Method Not Allowed
+
+%dl
+  %di
+    %dt Request URL
+    %dd= request.env['REQUEST_URI']
diff --git a/server/views/errors/not_allowed.xml.haml b/server/views/errors/not_allowed.xml.haml
new file mode 100644
index 0000000..3bad335
--- /dev/null
+++ b/server/views/errors/not_allowed.xml.haml
@@ -0,0 +1,2 @@
+%error{:url => "#{request.env['REQUEST_URI']}", :status => "#{response.status}"}
+  %message Method not allowed for this resource
-- 
1.7.2.3


[PATCH core 1/4] Added LoadBalancer object

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

---
 server/deltacloud.rb                          |    1 +
 server/lib/deltacloud/models/load_balancer.rb |   39 +++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 0 deletions(-)
 create mode 100644 server/lib/deltacloud/models/load_balancer.rb

diff --git a/server/deltacloud.rb b/server/deltacloud.rb
index 82e5780..02d6c2e 100644
--- a/server/deltacloud.rb
+++ b/server/deltacloud.rb
@@ -15,6 +15,7 @@ require 'deltacloud/models/storage_snapshot'
 require 'deltacloud/models/storage_volume'
 require 'deltacloud/models/bucket'
 require 'deltacloud/models/blob'
+require 'deltacloud/models/load_balancer'
 
 require 'deltacloud/validation'
 require 'deltacloud/helpers'
diff --git a/server/lib/deltacloud/models/load_balancer.rb b/server/lib/deltacloud/models/load_balancer.rb
new file mode 100644
index 0000000..546a25f
--- /dev/null
+++ b/server/lib/deltacloud/models/load_balancer.rb
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2009  Red Hat, Inc.
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+class LoadBalancer < BaseModel
+
+  attr_accessor :realms
+  attr_accessor :listeners
+  attr_accessor :instances
+  attr_accessor :public_addresses
+  attr_accessor :created_at
+
+  def add_listener(opts)
+    @listeners << Listener.new(opts)
+  end
+
+  class Listener < BaseModel
+    attr_accessor :protocol
+    attr_accessor :load_balancer_port
+    attr_accessor :instance_port
+  end
+
+end
+
-- 
1.7.3.2


[PATCH core 4/4] Added support for F5 load_balancer collection in GoGrid

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

---
 .../lib/deltacloud/drivers/gogrid/gogrid_client.rb |   11 +-
 .../lib/deltacloud/drivers/gogrid/gogrid_driver.rb |  119 +++++++++++++++++++-
 2 files changed, 120 insertions(+), 10 deletions(-)

diff --git a/server/lib/deltacloud/drivers/gogrid/gogrid_client.rb b/server/lib/deltacloud/drivers/gogrid/gogrid_client.rb
index c37f061..c463fa6 100644
--- a/server/lib/deltacloud/drivers/gogrid/gogrid_client.rb
+++ b/server/lib/deltacloud/drivers/gogrid/gogrid_client.rb
@@ -9,7 +9,7 @@ class GoGridClient
                  apikey='YOUR API KEY',
                  secret='YOUR SHARED SECRET', 
                  format='json',
-                 version='1.5')
+                 version='1.6')
     @server = server
     @secret = secret
     @default_params = {'format'=>format, 'v'=>version,'api_key' => apikey}
@@ -35,12 +35,9 @@ class GoGridClient
       @default_params['v'] = version
     else
       @default_params['v'] = '1.5'
-    end
-    begin
-      JSON::parse(sendAPIRequest(method, params))
-    rescue Exception => e
-      STDERR.puts("ERROR: #{e.message}")
-    end
+   end
+    request = sendAPIRequest(method, params)
+    JSON::parse(request)
   end
   
   def encode_params(params)
diff --git a/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb b/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb
index 0e25530..fd8e756 100644
--- a/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb
+++ b/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb
@@ -45,7 +45,7 @@ class GogridDriver < Deltacloud::BaseDriver
 
   def supported_collections
     DEFAULT_COLLECTIONS.reject! { |c| [ :storage_volumes, :storage_snapshots ].include?(c) }
-    DEFAULT_COLLECTIONS + [ :keys ]
+    DEFAULT_COLLECTIONS + [ :keys, :load_balancers ]
   end
 
   def images(credentials, opts=nil)
@@ -176,6 +176,88 @@ class GogridDriver < Deltacloud::BaseDriver
     end
   end
 
+ def create_load_balancer(credentials, opts={})
+    gogrid = new_client(credentials)
+    balancer, l_instance = nil, nil
+    safely do
+      virtip = get_free_ip_from_realm(credentials, opts['realm_id'])
+      if opts['instance_id']
+        l_instance = instance(credentials, :id => opts['instance_id'])
+        real_ip = {
+          'realiplist.0.port' => opts['listener_inst_port'],
+          'realiplist.0.ip' => l_instance ? l_instance.public_addresses.first : ""
+        }
+      else
+        real_ip = false
+      end
+      request = {
+        'name' => opts['name'],
+        'virtualip.ip' => virtip,
+        'virtualip.port' => opts['listener_lbr_port'],
+      }
+      request.merge!(real_ip) if real_ip
+      balancer = gogrid.request('grid/loadbalancer/add', request)['list'].first
+    end
+    balancer = convert_load_balancer(credentials, balancer)
+    balancer.instances = [l_instance] if l_instance
+    balancer
+  end
+
+  def destroy_load_balancer(credentials, id)
+    gogrid = new_client(credentials)
+    balancer = nil
+    safely do
+      balancer = gogrid.request('grid/loadbalancer/delete', { 'name' => id })
+      balancer = load_balancer(credentials, :id => id) unless balancer
+    end
+    convert_load_balancer(credentials, balancer)
+  end
+
+  def load_balancers(credentials, opts={})
+    gogrid = new_client(credentials)
+    balancers = []
+    safely do
+      balancer = gogrid.request('grid/loadbalancer/list', opts || {})['list'].each do |balancer|
+        balancers << balancer
+      end
+    end
+    balancers.collect { |b| convert_load_balancer(credentials, b) }
+  end
+
+  def load_balancer(credentials, opts={})
+    gogrid = new_client(credentials)
+    balancer = nil
+    begin
+      balancer = gogrid.request('grid/loadbalancer/get', { 'name' => opts[:id] })['list'].first
+      balancer['instances'] = instances(credentials)
+      return convert_load_balancer(credentials, balancer)
+    rescue OpenURI::HTTPError
+      balancer = load_balancers(credentials, :id => opts[:id]).first
+    end
+  end
+
+
+  def lb_register_instance(credentials, opts={})
+    client = new_client(credentials)
+    instance = instance(credentials, :id => opts[:instance_id])
+    balancer = client.request('grid/loadbalancer/get', { 'name' => opts[:id]})['list'].first
+    safely do
+      convert_load_balancer(credentials, client.request('grid/loadbalancer/edit', {
+        "id" => balancer['id'],
+        "realiplist.#{balancer['realiplist'].size}.ip" => instance.public_addresses.first,
+        "realiplist.#{balancer['realiplist'].size}.port" => balancer['virtualip']['port']
+      }))
+    end
+  end
+
+  # Move this to capabilities
+  def lb_unregister_instance(credentials, opts={})
+      raise Deltacloud::BackendFeatureUnsupported.new('501',
+    'Unregistering instances from load balancer is not supported in GoGrid')
+  end
+
+
+
   def key(credentials, opts=nil)
     keys(credentials, opts).first
   end
@@ -213,8 +295,39 @@ class GogridDriver < Deltacloud::BaseDriver
 
   def new_client(credentials)
     GoGridClient.new('https://api.gogrid.com/api', credentials.user, credentials.password)
+  end
 
+  def convert_load_balancer(credentials, loadbalancer)
+    if loadbalancer['datacenter']
+      b_realm = realm(credentials, :id => loadbalancer['datacenter']['id'])
+    else
+      # Report first Realm until loadbalancer become ready
+      b_realm = realm(credentials, :id => 1)
+    end
+    balancer = LoadBalancer.new({
+      :id => loadbalancer['name'],
+      :realms => [b_realm]
+    })
+    balancer.public_addresses = [loadbalancer['virtualip']['ip']['ip']] if loadbalancer['virtualip'] and loadbalancer['virtualip']['ip']
+    balancer.listeners = []
+    balancer.instances = []
+    instance_ips = []
+    loadbalancer['realiplist'].each do |instance_ip|
+      balancer.add_listener({
+        :protocol => 'TCP',
+        :load_balancer_port => loadbalancer['virtualip']['port'],
+        :instance_port => instance_ip['port']
+      })
+      instance_ips << instance_ip['ip']['ip']
+    end if loadbalancer['realiplist']
+    balancer.instances = get_load_balancer_instances(instance_ips, loadbalancer['instances'])
+    return balancer
   end
+ 
+  def get_load_balancer_instances(instance_ips, instances)
+    instances.select { |i| instance_ips.include?(i.public_addresses.first) } if instances
+  end
+
 
   def get_login_data(client, instance_id)
     login_data = {}
@@ -316,11 +429,11 @@ class GogridDriver < Deltacloud::BaseDriver
     state.eql?('Off') ? 'STOPPED' : 'RUNNING'
   end
 
-  def get_free_ip_from_realm(credentials, realm_id)
+  def get_free_ip_from_realm(credentials, realm_id, ip_type = 1)
     ip = ""
     safely do
       ip = new_client(credentials).request('grid/ip/list', {
-        'ip.type' => '1',
+        'ip.type' => "#{ip_type}",
         'ip.state' => '1',
         'datacenter' => realm_id
       })['list'].first['ip']
-- 
1.7.3.2


[PATCH core 1/3] Added 405 HTTP code when calling invalid instance action

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

---
 .../lib/deltacloud/helpers/application_helper.rb   |    8 ++++++++
 server/lib/deltacloud/models/instance.rb           |   20 +++++++++++++++-----
 2 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/server/lib/deltacloud/helpers/application_helper.rb b/server/lib/deltacloud/helpers/application_helper.rb
index c7fa481..189148c 100644
--- a/server/lib/deltacloud/helpers/application_helper.rb
+++ b/server/lib/deltacloud/helpers/application_helper.rb
@@ -102,6 +102,14 @@ module ApplicationHelper
   end
 
   def instance_action(name)
+    original_instance = driver.instance(credentials, :id => params[:id])
+
+    # If original instance doesn't include called action
+    # return with 405 error (Method is not Allowed)
+    unless driver.instance_actions_for(original_instance.state).include?(name.to_sym)
+      return report_error(405, 'not_allowed')
+    end
+
     @instance = driver.send(:"#{name}_instance", credentials, params["id"])
 
     return redirect(instances_url) if name.eql?(:destroy) or @instance.class!=Instance
diff --git a/server/lib/deltacloud/models/instance.rb b/server/lib/deltacloud/models/instance.rb
index 9cf69b8..0167331 100644
--- a/server/lib/deltacloud/models/instance.rb
+++ b/server/lib/deltacloud/models/instance.rb
@@ -29,10 +29,20 @@ class Instance < BaseModel
   attr_accessor :private_addresses
   attr_accessor :instance_profile
   attr_accessor :launch_time
- def initialize(init=nil)
-   super(init)
-   self.actions = [] if self.actions.nil?
-   self.public_addresses = [] if self.public_addresses.nil?
-   self.private_addresses = [] if self.private_addresses.nil?
+  
+  def initialize(init=nil)
+    super(init)
+    self.actions = [] if self.actions.nil?
+    self.public_addresses = [] if self.public_addresses.nil?
+    self.private_addresses = [] if self.private_addresses.nil?
   end
+
+  def method_missing(name, *args)
+    if name =~ /is_(\w+)\?/
+      return true if self.state.downcase.eql?($1)
+    else
+      raise NoMethodError
+    end
+  end
+
 end
-- 
1.7.2.3


[PATCH core 2/3] Fix Mock driver speed in case you have a lot of YAML files

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

---
 server/lib/deltacloud/drivers/mock/mock_driver.rb |   13 ++++++++++++-
 1 files changed, 12 insertions(+), 1 deletions(-)

diff --git a/server/lib/deltacloud/drivers/mock/mock_driver.rb b/server/lib/deltacloud/drivers/mock/mock_driver.rb
index 847319b..52c63b7 100644
--- a/server/lib/deltacloud/drivers/mock/mock_driver.rb
+++ b/server/lib/deltacloud/drivers/mock/mock_driver.rb
@@ -130,12 +130,23 @@ class MockDriver < Deltacloud::BaseDriver
   #
   # Instances
   #
+  require 'ruby-prof'
+
+  def instance(credentials, opts={})
+    check_credentials( credentials )
+    instance_filename = File.join(@storage_root, 'instances', "#{opts[:id]}.yml")
+    return nil unless File.exists?(instance_filename)
+    instance = YAML::load_file(instance_filename)
+    instance[:actions] = instance_actions_for( instance[:state] )
+    instance[:id] = File::basename(instance_filename, ".yml")
+    Instance.new(instance)
+  end
 
   def instances(credentials, opts=nil)
     check_credentials( credentials )
     instances = []
     Dir[ "#{@storage_root}/instances/*.yml" ].each do |instance_file|
-      instance = YAML.load( File.read( instance_file ) )
+      instance = YAML::load_file(instance_file)
       if ( instance[:owner_id] == credentials.user )
         instance[:id] = File.basename( instance_file, ".yml" )
         instance[:actions] = instance_actions_for( instance[:state] )
-- 
1.7.2.3


[PATCH core 3/4] Implemented Load Balancers to EC2 driver

Posted by mf...@redhat.com.
From: Michal Fojtik <mf...@redhat.com>

---
 server/lib/deltacloud/drivers/ec2/ec2_driver.rb |  112 ++++++++++++++++++++++-
 1 files changed, 108 insertions(+), 4 deletions(-)

diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
index 9b37069..9003b6d 100644
--- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
+++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
@@ -38,13 +38,14 @@ module Deltacloud
 class EC2Driver < Deltacloud::BaseDriver
 
   def supported_collections
-    DEFAULT_COLLECTIONS + [ :keys, :buckets ]
+    DEFAULT_COLLECTIONS + [ :keys, :buckets, :load_balancers ]
   end
 
   feature :instances, :user_data
   feature :instances, :authentication_key
   feature :images, :owner_id
   feature :buckets, :bucket_location
+  feature :instances, :register_to_load_balancer
 
   define_hardware_profile('m1.small') do
     cpu                1
@@ -194,7 +195,15 @@ class EC2Driver < Deltacloud::BaseDriver
         :disable_api_termination => false,
         :instance_initiated_shutdown_behavior => 'terminate'
       )
-      return convert_instance( ec2_instances.instancesSet.item.first, 'pending' )
+      new_instance = convert_instance( ec2_instances.instancesSet.item.first, 'pending' )
+      if opts[:load_balancer_id] and opts[:load_balancer_id]!=""
+        elb = new_client(credentials, :elb)
+        elb.register_instances_with_load_balancer({
+          :instances => [new_instance.id],
+          :load_balancer_name => opts[:load_balancer_id]
+        })
+      end
+      return new_instance
     end
   end
 
@@ -402,19 +411,114 @@ class EC2Driver < Deltacloud::BaseDriver
     end
   end
 
+  def load_balancer(credentials, opts={})
+    load_balancers(credentials, {
+      :load_balancer_names => [opts[:id]]
+    }).first
+  end
+
+  def load_balancers(credentials, opts=nil)
+    ec2 = new_client( credentials, :elb )
+    result = []
+    safely do
+      loadbalancers = ec2.describe_load_balancers(opts || {})
+      return [] unless loadbalancers.DescribeLoadBalancersResult.LoadBalancerDescriptions
+      loadbalancers.DescribeLoadBalancersResult.LoadBalancerDescriptions.member.each do |loadbalancer|
+        result << convert_load_balancer(credentials, loadbalancer)
+      end
+    end
+    return result
+  end
+
+  def create_load_balancer(credentials, opts={})
+    ec2 = new_client( credentials, :elb )
+    safely do
+      ec2.create_load_balancer({
+        :load_balancer_name => opts['name'],
+        # TODO: Add possibility to push more listeners/realms in one request
+        # Something like 'Hash' in 'Array' parameter
+        :availability_zones => [opts['realm_id']],
+        :listeners => [{
+          :protocol => opts['listener_protocol'],
+          :load_balancer_port => opts['listener_lbr_port'],
+          :instance_port => opts['listener_inst_port']
+         }]
+      })
+      return load_balancer(credentials, opts['name'])
+    end
+  end
+
+  def destroy_load_balancer(credentials, id)
+    ec2 = new_client( credentials, :elb )
+    safely do
+      ec2.delete_load_balancer({
+        :load_balancer_name => id
+      })
+    end
+  end
+
+  def lb_register_instance(credentials, opts={})
+    ec2 = new_client( credentials, :elb)
+    safely do
+      ec2.register_instances_with_load_balancer(:instances => [opts[:instance_id]],
+        :load_balancer_name => opts[:id])
+      load_balancer(credentials, :id => opts[:id])
+    end
+  end
+
+  def lb_unregister_instance(credentials, opts={})
+    ec2 = new_client( credentials, :elb)
+    safely do
+      ec2.deregister_instances_from_load_balancer(:instances => [opts[:instance_id]],
+        :load_balancer_name => opts[:id])
+      load_balancer(credentials, :id => opts[:id])
+    end
+  end
+
   private
 
-  def new_client(credentials)
+  def new_client(credentials, provider_type = :ec2)
     opts = {
       :access_key_id => credentials.user,
       :secret_access_key => credentials.password
     }
     opts[:server] = ENV['DCLOUD_EC2_URL'] if ENV['DCLOUD_EC2_URL']
     safely do
-      AWS::EC2::Base.new(opts)
+      case provider_type
+        when :ec2
+          AWS::EC2::Base.new(opts)
+        when :elb
+          AWS::ELB::Base.new(opts)
+      end
+    end
+  end
+
+  def convert_load_balancer(credentials, loadbalancer)
+    balancer_realms = loadbalancer.AvailabilityZones.member.collect do |m|
+      realm(credentials, m)
+    end
+    balancer = LoadBalancer.new({
+      :id => loadbalancer['LoadBalancerName'],
+      :created_at => loadbalancer['CreatedTime'],
+      :public_addresses => [loadbalancer['DNSName']],
+      :realms =>  balancer_realms
+    })
+    balancer.listeners = []
+    balancer.instances = []
+    loadbalancer.Listeners.member.each do |listener|
+      balancer.add_listener({
+        :protocol => listener['Protocol'],
+        :load_balancer_port => listener['LoadBalancerPort'],
+        :instance_port => listener['InstancePort']
+      })
     end
+    loadbalancer.Instances.member.each do |instance|
+      balancer.instances << instances(credentials, :id => instance['InstanceId']).first
+    end if loadbalancer.Instances
+    balancer
   end
 
+
   def convert_key(key)
     Key.new({
       :id => key['keyName'],
-- 
1.7.3.2