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 2011/05/02 13:40:56 UTC

[PATCH core] Added basic IP address managment support (for EC2)

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

---
 server/deltacloud.rb                            |    1 +
 server/lib/deltacloud/core_ext/string.rb        |    2 +
 server/lib/deltacloud/drivers/ec2/ec2_driver.rb |   52 +++++++++++++-
 server/lib/deltacloud/models/address.rb         |   28 +++++++
 server/server.rb                                |   86 +++++++++++++++++++++++
 server/views/addresses/associate.html.haml      |    8 ++
 server/views/addresses/index.html.haml          |   28 +++++++
 server/views/addresses/index.xml.haml           |    4 +
 server/views/addresses/show.html.haml           |   22 ++++++
 server/views/addresses/show.xml.haml            |   12 +++
 10 files changed, 242 insertions(+), 1 deletions(-)
 create mode 100644 server/lib/deltacloud/models/address.rb
 create mode 100644 server/views/addresses/associate.html.haml
 create mode 100644 server/views/addresses/index.html.haml
 create mode 100644 server/views/addresses/index.xml.haml
 create mode 100644 server/views/addresses/show.html.haml
 create mode 100644 server/views/addresses/show.xml.haml

diff --git a/server/deltacloud.rb b/server/deltacloud.rb
index 74fcce6..7caf34f 100644
--- a/server/deltacloud.rb
+++ b/server/deltacloud.rb
@@ -29,6 +29,7 @@ require 'deltacloud/models/realm'
 require 'deltacloud/models/image'
 require 'deltacloud/models/instance'
 require 'deltacloud/models/key'
+require 'deltacloud/models/address'
 require 'deltacloud/models/instance_profile'
 require 'deltacloud/models/storage_snapshot'
 require 'deltacloud/models/storage_volume'
diff --git a/server/lib/deltacloud/core_ext/string.rb b/server/lib/deltacloud/core_ext/string.rb
index 71514cb..42fbad0 100644
--- a/server/lib/deltacloud/core_ext/string.rb
+++ b/server/lib/deltacloud/core_ext/string.rb
@@ -33,10 +33,12 @@ class String
   end
 
   def pluralize
+    return self + 'es' if self =~ /ess$/
     self + "s"
   end
 
   def singularize
+    return self.gsub(/es$/, '') if self =~ /sses$/
     self.gsub(/s$/, '')
   end
 
diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
index 4b9899a..fc9ab73 100644
--- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
+++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
@@ -33,7 +33,7 @@ module Deltacloud
       class EC2Driver < Deltacloud::BaseDriver
 
         def supported_collections
-          DEFAULT_COLLECTIONS + [ :keys, :buckets, :load_balancers ]
+          DEFAULT_COLLECTIONS + [ :keys, :buckets, :load_balancers, :addresses ]
         end
 
         feature :instances, :user_data
@@ -509,6 +509,56 @@ module Deltacloud
           end
         end
 
+        def addresses(credentials, opts={})
+          ec2 = new_client(credentials)
+          address_id = (opts and opts[:id]) ? opts[:id] : []
+          safely do
+            ec2.describe_addresses(address_id).collect do |address|
+              Address.new(:id => address[:public_ip], :instance_id => address[:instance_id])
+            end
+          end
+        end
+
+        def address(credentials, opts={})
+          addresses(credentials, :id => opts[:id]).first
+        end
+
+        def create_address(credentials, opts={})
+          ec2 = new_client(credentials)
+          safely do
+            Address.new(:id => ec2.allocate_address)
+          end
+        end
+
+        def destroy_address(credentials, opts={})
+          ec2 = new_client(credentials)
+          safely do
+            ec2.release_address(opts[:id])
+          end
+        end
+
+        def associate_address(credentials, opts={})
+          ec2 = new_client(credentials)
+          safely do
+            if ec2.associate_address(opts[:instance_id], opts[:id])
+              Address.new(:id => opts[:id], :instance_id => opts[:instance_id])
+            else
+              raise "ERROR: Cannot associate IP address to an Instance"
+            end
+          end
+        end
+
+        def disassociate_address(credentials, opts={})
+          ec2 = new_client(credentials)
+          safely do
+            if ec2.disassociate_address(opts[:id])
+              Address.new(:id => opts[:id])
+            else
+              raise "ERROR: Cannot disassociate an IP address from the Instance"
+            end
+          end
+        end
+
         def valid_credentials?(credentials)
           retval = true
           begin
diff --git a/server/lib/deltacloud/models/address.rb b/server/lib/deltacloud/models/address.rb
new file mode 100644
index 0000000..dbabb08
--- /dev/null
+++ b/server/lib/deltacloud/models/address.rb
@@ -0,0 +1,28 @@
+#
+# 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 Address < BaseModel
+  attr_accessor :instance_id
+
+  def initialize(init=nil)
+    super(init)
+  end
+
+  def associated?
+    true unless self.instance_id.nil?
+  end
+
+end
diff --git a/server/server.rb b/server/server.rb
index dcc76e5..344d4bf 100644
--- a/server/server.rb
+++ b/server/server.rb
@@ -858,3 +858,89 @@ collection :buckets do
   end
 
 end
+
+get '/api/addresses/:id/associate' do
+  @instances = driver.instances(credentials)
+  @address = Address::new(:id => params[:id])
+  respond_to do |format|
+    format.html { haml :"addresses/associate" }
+  end
+end
+
+collection :addresses do
+  description "Manage IP addresses"
+
+  operation :index do
+    description "List IP addresses assigned to your account."
+    with_capability :addresses
+    control do
+      filter_all :addresses
+    end
+  end
+
+  operation :show do
+    description "Show details about IP addresses specified by given ID"
+    with_capability :address
+    param :id,  :string,  :required
+    control { show :address }
+  end
+
+  operation :create do
+    description "Acquire a new IP address for use with your account."
+    with_capability :create_address
+    control do
+      @address = driver.create_address(credentials, {})
+      respond_to do |format|
+        format.html { haml :"addresses/show" }
+        format.xml do
+          response.status = 201  # Created
+          haml :"addresses/show", :ugly => true
+        end
+      end
+    end
+  end
+
+  operation :destroy do
+    description "Release an IP address associated with your account"
+    with_capability :destroy_address
+    param :id,  :string,  :required
+    control do
+      driver.destroy_address(credentials, { :id => params[:id]})
+      respond_to do |format|
+        format.xml { 204 }
+        format.json { 204 }
+        format.html { redirect(addresses_url) }
+      end
+    end
+  end
+
+  operation :associate, :method => :post, :member => true do
+    description "Associate an IP address to an instance"
+    with_capability :associate_address
+    param :id, :string, :required
+    param :instance_id, :string, :required
+    control do
+      driver.associate_address(credentials, { :id => params[:id], :instance_id => params[:instance_id]})
+      respond_to do |format|
+        format.xml { 202 }  # Accepted
+        format.json { 202 }
+        format.html { redirect(address_url(params[:id])) }
+      end
+    end
+  end
+
+  operation :disassociate, :method => :post, :member => true do
+    description "Disassociate an IP address from an instance"
+    with_capability :associate_address
+    param :id, :string, :required
+    control do
+      driver.disassociate_address(credentials, { :id => params[:id] })
+      respond_to do |format|
+        format.xml { 202 }  # Accepted
+        format.json { 202 }
+        format.html { redirect(address_url(params[:id])) }
+      end
+    end
+  end
+
+end
diff --git a/server/views/addresses/associate.html.haml b/server/views/addresses/associate.html.haml
new file mode 100644
index 0000000..9d1a92d
--- /dev/null
+++ b/server/views/addresses/associate.html.haml
@@ -0,0 +1,8 @@
+%h1 Associate #{@address.id} to an instance
+
+%form{ :action => associate_address_url(@address.id), :method => :post, :class => :new_instance }
+  %select{:name => 'instance_id'}
+    %option
+    - @instances.each do |inst|
+      %option{ :value => inst.id } #{inst.id} - #{inst.name}
+    %input{ :type => :submit, :name => "commit", :value => "Associate" }/
diff --git a/server/views/addresses/index.html.haml b/server/views/addresses/index.html.haml
new file mode 100644
index 0000000..acc1bb6
--- /dev/null
+++ b/server/views/addresses/index.html.haml
@@ -0,0 +1,28 @@
+%h1 Addresses
+
+%table.display
+  %thead
+    %tr
+      %th ID
+      %th Instance
+      %th Actions
+  %tbody
+    - @elements.each do |address|
+      %tr
+        %td
+          = link_to address.id, address_url( address.id )
+        %td
+          - if address.instance_id
+            = link_to address.instance_id, instance_url( address.instance_id )
+        %td
+          - if driver.respond_to?(:destroy_address)
+            =link_to_action 'Destroy', destroy_address_url(address.id), :delete
+          - if driver.respond_to?(:associate_address) and not address.associated?
+            =link_to_action 'Associate', associate_address_url(address.id), :get
+          - if driver.respond_to?(:disassociate_address)
+            =link_to_action 'Disassociate', disassociate_address_url(address.id), :post
+  %tfoot
+    - if driver.respond_to?(:create_address)
+      %tr
+        %td{:colspan => 3, :style => "text-align:right;"}
+          =link_to_action "Create", create_address_url, :post
diff --git a/server/views/addresses/index.xml.haml b/server/views/addresses/index.xml.haml
new file mode 100644
index 0000000..e2a564f
--- /dev/null
+++ b/server/views/addresses/index.xml.haml
@@ -0,0 +1,4 @@
+!!!XML
+%addresses
+  - @elements.each do |c|
+    = haml :'address/show', :locals => { :@address => c, :partial => true }
diff --git a/server/views/addresses/show.html.haml b/server/views/addresses/show.html.haml
new file mode 100644
index 0000000..105c41a
--- /dev/null
+++ b/server/views/addresses/show.html.haml
@@ -0,0 +1,22 @@
+%h1 Address
+
+%dl
+  %di
+    %dt Address
+    %dd=@address.id
+  %di
+    %dt Instance
+    %dd
+      - if @address.associated?
+        =link_to @address.instance_id, instance_url(@address.instance_id)
+        =link_to_action 'Disassociate', disassociate_address_url(@address.id), :post
+      - else
+        - if driver.respond_to?(:associate_address)
+          =link_to_action 'Associate', associate_address_url(@address.id), :get
+
+  %di
+    %dt Actions
+    %dd
+    - if driver.respond_to?(:destroy_address)
+      =link_to_action 'Destroy', destroy_address_url(@address.id), :delete
+
diff --git a/server/views/addresses/show.xml.haml b/server/views/addresses/show.xml.haml
new file mode 100644
index 0000000..a450690
--- /dev/null
+++ b/server/views/addresses/show.xml.haml
@@ -0,0 +1,12 @@
+- unless defined?(partial)
+  !!! XML
+%address{ :href => address_url(@address.id), :id => @address.id }
+  %actions
+    - if driver.respond_to?(:destroy_address)
+      %link{ :rel => "destroy", :method => "delete", :href => destroy_address_url(@address.id)}
+    - if driver.respond_to?(:associate_address) and not @address.instance_id
+      %link{ :rel => "associate", :method => "post", :href => associate_address_url(@address.id)}
+    - if driver.respond_to?(:disassociate_address) and @address.instance_id
+      %link{ :rel => "disassociate", :method => "post", :href => disassociate_address_url(@address.id)}
+  - if @address.instance_id
+    %instance{ :href => instance_url(@address.instance_id), :id => @address.instance_id}
-- 
1.7.4.1


Re: [PATCH core] Added basic IP address managment support (for EC2)

Posted by Michal Fojtik <mf...@redhat.com>.
On May 3, 2011, at 1:25 AM, David Lutterkort wrote:

> On Mon, 2011-05-02 at 13:40 +0200, mfojtik@redhat.com wrote:
>> From: Michal Fojtik <mf...@redhat.com>
> 
> ACK after addressing the comments below:
> 
>> diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
>> index 4b9899a..fc9ab73 100644
>> --- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
>> +++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
>> @@ -509,6 +509,56 @@ module Deltacloud
>>           end
>>         end
>> 
>> +        def addresses(credentials, opts={})
>> +          ec2 = new_client(credentials)
>> +          address_id = (opts and opts[:id]) ? opts[:id] : []
> 
> Shouldn't address_id always be an array, i.e.,
> 
>        address_id = (opts and opts[:id]) ? [opts[:id]] : []
> 
> Also a request for http://localhost:3001/api/addresses?id=127.0.0.1
> leads to a backend error (error code 502)
> 
> It would be more natural to me if that just resulted in an empty list.
> 
> A request for http://localhost:3001/api/addresses/127.0.0.1 also leads
> to a 502; that should be a 404

Fixed. Thanks for pointing to this.

> 
>> diff --git a/server/server.rb b/server/server.rb
>> index dcc76e5..344d4bf 100644
>> --- a/server/server.rb
>> +++ b/server/server.rb
>> @@ -858,3 +858,89 @@ collection :buckets do
>>   end
>> 
>> end
>> +
>> +get '/api/addresses/:id/associate' do
>> +  @instances = driver.instances(credentials)
>> +  @address = Address::new(:id => params[:id])
>> +  respond_to do |format|
>> +    format.html { haml :"addresses/associate" }
>> +  end
>> +end
>> +
>> +collection :addresses do
>> +  description "Manage IP addresses"
>> +
>> +  operation :index do
>> +    description "List IP addresses assigned to your account."
>> +    with_capability :addresses
>> +    control do
>> +      filter_all :addresses
>> +    end
>> +  end
>> +
>> +  operation :show do
>> +    description "Show details about IP addresses specified by given ID"
>> +    with_capability :address
>> +    param :id,  :string,  :required
>> +    control { show :address }
>> +  end
>> +
>> +  operation :create do
>> +    description "Acquire a new IP address for use with your account."
>> +    with_capability :create_address
>> +    control do
>> +      @address = driver.create_address(credentials, {})
>> +      respond_to do |format|
>> +        format.html { haml :"addresses/show" }
>> +        format.xml do
>> +          response.status = 201  # Created
> 
> Also set the Location header to the URL for the new address.

Header set.

> 
> 
>> diff --git a/server/views/addresses/index.xml.haml b/server/views/addresses/index.xml.haml
>> new file mode 100644
>> index 0000000..e2a564f
>> --- /dev/null
>> +++ b/server/views/addresses/index.xml.haml
>> @@ -0,0 +1,4 @@
>> +!!!XML
>> +%addresses
>> +  - @elements.each do |c|
>> +    = haml :'address/show', :locals => { :@address => c, :partial => true }
> 
> Typo:
>                ^^^^ addresses/show


Fixed.

> 
>> diff --git a/server/views/addresses/show.xml.haml b/server/views/addresses/show.xml.haml
>> new file mode 100644
>> index 0000000..a450690
>> --- /dev/null
>> +++ b/server/views/addresses/show.xml.haml
>> @@ -0,0 +1,12 @@
>> +- unless defined?(partial)
>> +  !!! XML
>> +%address{ :href => address_url(@address.id), :id => @address.id }
> 
> We should provide the IP as a separate attribute; there's no need to
> cement ID == IP in the API.

Right now there no other way how to 'identify' IP address in EC2.
For us that means we don't have any other 'attribute' we can use as an 'id'

Also we are using this format for all other objects (:id => @object.id).

I put a new element called '<ip>xxx.xxx.xxx.xxx</ip>' inside <address> block.

Thanks! I'll push this patchset today.

  -- Michal

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


Re: [PATCH core] Added basic IP address managment support (for EC2)

Posted by David Lutterkort <lu...@redhat.com>.
On Mon, 2011-05-02 at 13:40 +0200, mfojtik@redhat.com wrote:
> From: Michal Fojtik <mf...@redhat.com>

ACK after addressing the comments below:
 
> diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
> index 4b9899a..fc9ab73 100644
> --- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
> +++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
> @@ -509,6 +509,56 @@ module Deltacloud
>            end
>          end
>  
> +        def addresses(credentials, opts={})
> +          ec2 = new_client(credentials)
> +          address_id = (opts and opts[:id]) ? opts[:id] : []

Shouldn't address_id always be an array, i.e.,

        address_id = (opts and opts[:id]) ? [opts[:id]] : []

Also a request for http://localhost:3001/api/addresses?id=127.0.0.1
leads to a backend error (error code 502)

It would be more natural to me if that just resulted in an empty list.

A request for http://localhost:3001/api/addresses/127.0.0.1 also leads
to a 502; that should be a 404

> diff --git a/server/server.rb b/server/server.rb
> index dcc76e5..344d4bf 100644
> --- a/server/server.rb
> +++ b/server/server.rb
> @@ -858,3 +858,89 @@ collection :buckets do
>    end
>  
>  end
> +
> +get '/api/addresses/:id/associate' do
> +  @instances = driver.instances(credentials)
> +  @address = Address::new(:id => params[:id])
> +  respond_to do |format|
> +    format.html { haml :"addresses/associate" }
> +  end
> +end
> +
> +collection :addresses do
> +  description "Manage IP addresses"
> +
> +  operation :index do
> +    description "List IP addresses assigned to your account."
> +    with_capability :addresses
> +    control do
> +      filter_all :addresses
> +    end
> +  end
> +
> +  operation :show do
> +    description "Show details about IP addresses specified by given ID"
> +    with_capability :address
> +    param :id,  :string,  :required
> +    control { show :address }
> +  end
> +
> +  operation :create do
> +    description "Acquire a new IP address for use with your account."
> +    with_capability :create_address
> +    control do
> +      @address = driver.create_address(credentials, {})
> +      respond_to do |format|
> +        format.html { haml :"addresses/show" }
> +        format.xml do
> +          response.status = 201  # Created

Also set the Location header to the URL for the new address.


> diff --git a/server/views/addresses/index.xml.haml b/server/views/addresses/index.xml.haml
> new file mode 100644
> index 0000000..e2a564f
> --- /dev/null
> +++ b/server/views/addresses/index.xml.haml
> @@ -0,0 +1,4 @@
> +!!!XML
> +%addresses
> +  - @elements.each do |c|
> +    = haml :'address/show', :locals => { :@address => c, :partial => true }

Typo:
                ^^^^ addresses/show

> diff --git a/server/views/addresses/show.xml.haml b/server/views/addresses/show.xml.haml
> new file mode 100644
> index 0000000..a450690
> --- /dev/null
> +++ b/server/views/addresses/show.xml.haml
> @@ -0,0 +1,12 @@
> +- unless defined?(partial)
> +  !!! XML
> +%address{ :href => address_url(@address.id), :id => @address.id }

We should provide the IP as a separate attribute; there's no need to
cement ID == IP in the API.

David