deltacloud-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Michal Fojtik <mfoj...@redhat.com>
Subject CIMI::Models split to CIMI::Services (rev 2) WIP
Date Fri, 01 Mar 2013 13:40:01 GMT
Hi,

This is an extended version of David patch. The goal here
is to migrate all CIMI 'service' methods to CIMI::Service
models and to make CIMI::Model contain just schema.

This patch adds/remove/breaks:

- ResourceMetada are now broken and need to be fixed.
  (an informative warning included ;-)

- 5/9 should be merged to 8/9 (just note to myself ;-)

- The 'parse' method added to CIMI::Service (we use it in Create)

- The filter_by and select_by methods moved to .find, so no longer
  ugly chains in Rabbit collections.

NOTE: This patch is 'work in progress'. Many things could be possibly
be broken and at least ResourceMetadata must be fixed.

David: Can you have look on RMD? I think we reached the problem with
       lambdas :-)

  -- Michal


On 03/01, mfojtik@redhat.com wrote:
> From: David Lutterkort <lutter@redhat.com>
> 
> The current CIMI::Model classes address two concerns:
> 
>   * serialization/deserialization of CIMI objects
>   * interaction with the current driver and the DB
> 
> This patch splits these two concerns into two separate class hierarchies:
> CIMI::Model for (de)serialization and CIMI::Service for interacting with
> drivers/db
> 
> Besides cleaning up the code, this will make it possible to reuse
> CIMI::Model classes as the basis for client code.
> 
> Signed-off-by: Michal fojtik <mfojtik@redhat.com>
> ---
>  server/lib/cimi/collections/base.rb  |   4 +-
>  server/lib/cimi/models/collection.rb |  27 ++----
>  server/lib/cimi/models/machine.rb    | 125 --------------------------
>  server/lib/cimi/service.rb           |  21 +++++
>  server/lib/cimi/service/base.rb      | 169 +++++++++++++++++++++++++++++++++++
>  server/lib/cimi/service/machine.rb   | 147 ++++++++++++++++++++++++++++++
>  6 files changed, 347 insertions(+), 146 deletions(-)
>  create mode 100644 server/lib/cimi/service.rb
>  create mode 100644 server/lib/cimi/service/base.rb
>  create mode 100644 server/lib/cimi/service/machine.rb
> 
> diff --git a/server/lib/cimi/collections/base.rb b/server/lib/cimi/collections/base.rb
> index eac1c70..3c319d6 100644
> --- a/server/lib/cimi/collections/base.rb
> +++ b/server/lib/cimi/collections/base.rb
> @@ -13,7 +13,7 @@
>  # License for the specific language governing permissions and limitations
>  # under the License.
>  
> -require_relative '../models'
> +require_relative '../service'
>  
>  module CIMI::Collections
>    class Base < Sinatra::Base
> @@ -24,7 +24,7 @@ module CIMI::Collections
>  
>      include Sinatra::Rabbit
>      include Sinatra::Rabbit::Features
> -    include CIMI::Model
> +    include CIMI::Service
>  
>      helpers Deltacloud::Helpers::Drivers
>      helpers Deltacloud::Helpers::Database
> diff --git a/server/lib/cimi/models/collection.rb b/server/lib/cimi/models/collection.rb
> index f36c081..cc95ab7 100644
> --- a/server/lib/cimi/models/collection.rb
> +++ b/server/lib/cimi/models/collection.rb
> @@ -120,26 +120,15 @@ module CIMI::Model
>      end
>  
>      # Return a collection of entities
> -    def list(context)
> -      entries = find(:all, context)
> -      desc = "#{self.name.split("::").last} Collection for the #{context.driver.name.capitalize}
driver"
> -      acts_as_root_entity unless collection_class
> -      id = context.send("#{collection_class.entry_name}_url")
> -      ops = []
> -      cimi_entity = collection_class.entry_name.to_s.singularize
> -      cimi_create = "create_#{cimi_entity}_url"
> -      dcloud_create = context.deltacloud_create_method_for(cimi_entity)
> -      if(context.respond_to?(cimi_create) &&
> -         context.driver.respond_to?(dcloud_create)) ||
> -      provides?(cimi_entity)
> -        url = context.send(cimi_create)
> -        ops << { :rel => "add", :href => url }
> +    def list(id, entries, params = {})
> +      params[:id] = id
> +      params[:entries] = entries
> +      params[:count] = params[:entries].size
> +      if params[:add_url]
> +        params[:operations] ||= []
> +        params[:operations] << { :rel => "add", :href => params.delete(:add_url)
}
>        end
> -      collection_class.new(:id => id,
> -                           :count => entries.size,
> -                           :entries => entries,
> -                           :operations => ops,
> -                           :description => desc)
> +      collection_class.new(params)
>      end
>  
>      def all(context)
> diff --git a/server/lib/cimi/models/machine.rb b/server/lib/cimi/models/machine.rb
> index 3beb2f7..6990fb9 100644
> --- a/server/lib/cimi/models/machine.rb
> +++ b/server/lib/cimi/models/machine.rb
> @@ -38,129 +38,4 @@ class CIMI::Model::Machine < CIMI::Model::Base
>      scalar :rel, :href
>    end
>  
> -  def self.find(id, context)
> -    instances = []
> -    if id == :all
> -      instances = context.driver.instances(context.credentials)
> -      instances.map { |instance| from_instance(instance, context) }.compact
> -    else
> -      instance = context.driver.instance(context.credentials, :id => id)
> -      raise CIMI::Model::NotFound unless instance
> -      from_instance(instance, context)
> -    end
> -  end
> -
> -  def perform(action, context, &block)
> -    begin
> -      if context.driver.send(:"#{action.name}_instance", context.credentials, self.id.split("/").last)
> -        block.callback :success
> -      else
> -        raise "Operation failed to execute on given Machine"
> -      end
> -    rescue => e
> -      block.callback :failure, e.message
> -    end
> -  end
> -
> -  def self.delete!(id, context)
> -    context.driver.destroy_instance(context.credentials, id)
> -    new(:id => id).destroy
> -  end
> -
> -  #returns the newly attach machine_volume
> -  def self.attach_volume(volume, location, context)
> -    context.driver.attach_storage_volume(context.credentials,
> -     {:id=>volume, :instance_id=>context.params[:id], :device=>location})
> -    CIMI::Model::MachineVolume.find(context.params[:id], context, volume)
> -  end
> -
> -  #returns the machine_volume_collection for the given machine
> -  def self.detach_volume(volume, location, context)
> -    context.driver.detach_storage_volume(context.credentials,
> -     {:id=>volume, :instance_id=>context.params[:id], :device=>location})
> -    CIMI::Model::MachineVolume.collection_for_instance(context.params[:id], context)
> -  end
> -
> -  def self.from_instance(instance, context)
> -    cpu =  memory = (instance.instance_profile.id == "opaque")? "n/a" : nil
> -    machine_conf = CIMI::Model::MachineConfiguration.find(instance.instance_profile.name,
context)
> -    machine_spec = {
> -      :name => instance.name,
> -      :created => instance.launch_time.nil? ? Time.now.xmlschema : Time.parse(instance.launch_time.to_s).xmlschema,
> -      :description => "No description set for Machine #{instance.name}",
> -      :id => context.machine_url(instance.id),
> -      :state => convert_instance_state(instance.state),
> -      :cpu => cpu || convert_instance_cpu(instance.instance_profile, context),
> -      :memory => memory || convert_instance_memory(instance.instance_profile, context),
> -      :disks => { :href => context.machine_url(instance.id)+"/disks"},
> -      :volumes => { :href=>context.machine_url(instance.id)+"/volumes"},
> -      :operations => convert_instance_actions(instance, context)
> -    }
> -    if context.expand? :disks
> -      machine_spec[:disks] = CIMI::Model::Disk.find(instance, machine_conf, context,
:all)
> -    end
> -    if context.expand? :volumes
> -      machine_spec[:volumes] = CIMI::Model::MachineVolume.find(instance.id, context,
:all)
> -    end
> -    machine_spec[:realm] = instance.realm_id if instance.realm_id
> -    machine_spec[:machine_image] = { :href => context.machine_image_url(instance.image_id)
} if instance.image_id
> -    self.new(machine_spec)
> -  end
> -
> -  # FIXME: This will convert 'RUNNING' state to 'STARTED'
> -  # which is defined in CIMI (p65)
> -  #
> -  def self.convert_instance_state(state)
> -    case state
> -      when "RUNNING" then "STARTED"
> -      when "PENDING" then "CREATING" #aruba only exception... could be "STARTING" here
> -      else state
> -    end
> -  end
> -
> -  def self.convert_instance_cpu(profile, context)
> -    cpu_override = profile.overrides.find { |p, v| p == :cpu }
> -    if cpu_override.nil?
> -      CIMI::Model::MachineConfiguration.find(profile.id, context).cpu
> -    else
> -      cpu_override[1]
> -    end
> -  end
> -
> -  def self.convert_instance_memory(profile, context)
> -    machine_conf = CIMI::Model::MachineConfiguration.find(profile.name, context)
> -    memory_override = profile.overrides.find { |p, v| p == :memory }
> -    memory_override.nil? ? machine_conf.memory.to_i : context.to_kibibyte(memory_override[1].to_i,"MB")
> -  end
> -
> -  def self.convert_instance_addresses(instance)
> -    (instance.public_addresses + instance.private_addresses).collect do |address|
> -      {
> -        :hostname => address.is_hostname? ? address : nil,
> -        :mac_address => address.is_mac? ? address : nil,
> -        :state => 'Active',
> -        :protocol => 'IPv4',
> -        :address => address.is_ipv4? ? address : nil,
> -        :allocation => 'Static'
> -      }
> -    end
> -  end
> -
> -  def self.convert_instance_actions(instance, context)
> -    actions = instance.actions.collect do |action|
> -      action = :restart if action == :reboot
> -      name = action
> -      name = :delete if action == :destroy # In CIMI destroy operation become delete
> -      { :href => context.send(:"#{action}_machine_url", instance.id), :rel =>
"http://schemas.dmtf.org/cimi/1/action/#{name}" }
> -    end
> -    actions <<  { :href => context.send(:"machine_images_url"), :rel =>
"http://schemas.dmtf.org/cimi/1/action/capture" } if instance.can_create_image?
> -    actions
> -  end
> -
> -  def self.convert_storage_volumes(instance, context)
> -    instance.storage_volumes ||= [] #deal with nilpointers
> -    instance.storage_volumes.map{|vol| {:href=>context.volume_url(vol.keys.first),
> -                                       :initial_location=>vol.values.first} }
> -  end
> -
>  end
> diff --git a/server/lib/cimi/service.rb b/server/lib/cimi/service.rb
> new file mode 100644
> index 0000000..c9b662d
> --- /dev/null
> +++ b/server/lib/cimi/service.rb
> @@ -0,0 +1,21 @@
> +# 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 require_relatived 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.
> +#
> +
> +module CIMI::Service; end
> +
> +require_relative './models'
> +require_relative './service/base'
> +require_relative './service/machine'
> diff --git a/server/lib/cimi/service/base.rb b/server/lib/cimi/service/base.rb
> new file mode 100644
> index 0000000..9c281b7
> --- /dev/null
> +++ b/server/lib/cimi/service/base.rb
> @@ -0,0 +1,169 @@
> +# 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.
> +
> +require 'xmlsimple'
> +
> +require_relative '../helpers/database_helper'
> +
> +# Service objects implement the server functionality of CIMI resources; in
> +# particular, these objects are responsible for interacting with the
> +# current driver. They use the CIMI::Model objects for (de)serialization
> +module CIMI::Service
> +
> +  class Base
> +
> +    # Extend the base model with database methods
> +    extend Deltacloud::Helpers::Database
> +
> +    attr_reader :model, :context
> +
> +    class << self
> +      def model_class
> +        CIMI::Model.const_get(name.split('::').last)
> +      end
> +
> +      def model_name
> +        name.split('::').last.underscore.to_sym
> +      end
> +
> +      def collection_name
> +        name.split('::').last.underscore.pluralize.to_sym
> +      end
> +
> +      def inherited(subclass)
> +        # Decorate all the attributes of the model class
> +        schema = subclass.model_class.schema
> +        schema.attribute_names.each do |name|
> +          define_method(name) { self[name] }
> +          define_method(:"#{name}=") { |newval| self[name] = newval }
> +        end
> +      end
> +    end
> +
> +    def initialize(context, opts)
> +      if opts[:values]
> +        @model = model_class.new(opts[:values])
> +      elsif opts[:model]
> +        @model = opts[:model]
> +      else
> +        @model = model_class.new({})
> +      end
> +      @context = context
> +      retrieve_entity
> +    end
> +
> +    def model_class
> +      self.class.model_class
> +    end
> +
> +    # Decorate some model methods
> +    def []=(a, v)
> +      v = (@model[a] = v)
> +      retrieve_entity if a == :id
> +      v
> +    end
> +
> +    def [](a)
> +      @model[a]
> +    end
> +
> +    def to_xml
> +      @model.to_xml
> +    end
> +
> +    def to_json
> +      @model.to_json
> +    end
> +
> +    def select_attributes(attr_list)
> +      @model.select_attributes(attr_list)
> +    end
> +
> +    def self.list(ctx)
> +      id = ctx.send("#{collection_name}_url")
> +      entries = find(:all, ctx)
> +      params = {}
> +      params[:desc] = "#{self.name.split("::").last} Collection for the #{ctx.driver.name.capitalize}
driver"
> +      params[:add_url] = create_url(ctx)
> +      model_class.list(id, entries, params)
> +    end
> +
> +    def self.create_url(ctx)
> +      cimi_create = "create_#{model_name}_url"
> +      dcloud_create = ctx.deltacloud_create_method_for(model_name)
> +      if(ctx.respond_to?(cimi_create) &&
> +         ctx.respond_to?(dcloud_create)) || provides?(model_name)
> +        ctx.send(cimi_create)
> +      end
> +    end
> +
> +    # Save the common attributes name, description, and properties to the
> +    # database
> +    def save
> +      if @entity
> +        @entity.name = @model.name
> +        @entity.description = @model.description
> +        @entity.properties = @model.property
> +        @entity.save
> +      end
> +      self
> +    end
> +
> +    # Destroy the database attributes for this model
> +    def destroy
> +      @entity.destroy
> +      self
> +    end
> +
> +    # FIXME: Kludge around the fact that we do not have proper *Create
> +    # objects that deserialize properties by themselves
> +    def extract_properties!(data)
> +      h = {}
> +      if data['property']
> +        # Data came from XML
> +        h = data['property'].inject({}) do |r,v|
> +          r[v['key']] = v['content']
> +          r
> +        end
> +      elsif data['properties']
> +        h = data['properties']
> +      end
> +      property ||= {}
> +      property.merge!(h)
> +    end
> +
> +    def ref_id(ref_url)
> +      ref_url.split('/').last if ref_url
> +    end
> +
> +    private
> +
> +    # Load an existing database entity for this object, or create a new one
> +    def retrieve_entity
> +      if self.id
> +        @entity = Deltacloud::Database::Entity::retrieve(self)
> +        if @entity.exists?
> +          @model.name = @entity.name
> +          @model.description = @entity.description
> +          @model.property ||= {}
> +          @model.property.merge!(@entity.properties)
> +        end
> +      else
> +        @entity = nil
> +      end
> +    end
> +
> +  end
> +end
> diff --git a/server/lib/cimi/service/machine.rb b/server/lib/cimi/service/machine.rb
> new file mode 100644
> index 0000000..dedb0ad
> --- /dev/null
> +++ b/server/lib/cimi/service/machine.rb
> @@ -0,0 +1,147 @@
> +# 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 CIMI::Service::Machine < CIMI::Service::Base
> +
> +  def initialize(ctx, opts)
> +    super
> +  end
> +
> +  def self.find(id, context)
> +    instances = []
> +    if id == :all
> +      instances = context.driver.instances(context.credentials)
> +      instances.map { |instance| from_instance(instance, context) }.compact
> +    else
> +      instance = context.driver.instance(context.credentials, :id => id)
> +      raise CIMI::Model::NotFound unless instance
> +      from_instance(instance, context)
> +    end
> +  end
> +
> +  def perform(action, context, &block)
> +    begin
> +      if context.driver.send(:"#{action.name}_instance", context.credentials, self.id.split("/").last)
> +        block.callback :success
> +      else
> +        raise "Operation failed to execute on given Machine"
> +      end
> +    rescue => e
> +      block.callback :failure, e.message
> +    end
> +  end
> +
> +  def self.delete!(id, context)
> +    context.driver.destroy_instance(context.credentials, id)
> +    new(:id => id).destroy
> +  end
> +
> +  #returns the newly attach machine_volume
> +  def self.attach_volume(volume, location, context)
> +    context.driver.attach_storage_volume(context.credentials,
> +     {:id=>volume, :instance_id=>context.params[:id], :device=>location})
> +    CIMI::Model::MachineVolume.find(context.params[:id], context, volume)
> +  end
> +
> +  #returns the machine_volume_collection for the given machine
> +  def self.detach_volume(volume, location, context)
> +    context.driver.detach_storage_volume(context.credentials,
> +     {:id=>volume, :instance_id=>context.params[:id], :device=>location})
> +    CIMI::Model::MachineVolume.collection_for_instance(context.params[:id], context)
> +  end
> +
> +  def self.from_instance(instance, context)
> +    cpu =  memory = (instance.instance_profile.id == "opaque")? "n/a" : nil
> +    machine_conf = CIMI::Model::MachineConfiguration.find(instance.instance_profile.name,
context)
> +    machine_spec = {
> +      :name => instance.name,
> +      :created => instance.launch_time.nil? ? Time.now.xmlschema : Time.parse(instance.launch_time.to_s).xmlschema,
> +      :description => "No description set for Machine #{instance.name}",
> +      :id => context.machine_url(instance.id),
> +      :state => convert_instance_state(instance.state),
> +      :cpu => cpu || convert_instance_cpu(instance.instance_profile, context),
> +      :memory => memory || convert_instance_memory(instance.instance_profile, context),
> +      :disks => { :href => context.machine_url(instance.id)+"/disks"},
> +      :volumes => { :href=>context.machine_url(instance.id)+"/volumes"},
> +      :operations => convert_instance_actions(instance, context)
> +    }
> +    if context.expand? :disks
> +      machine_spec[:disks] = CIMI::Model::Disk.find(instance, machine_conf, context,
:all)
> +    end
> +    if context.expand? :volumes
> +      machine_spec[:volumes] = CIMI::Model::MachineVolume.find(instance.id, context,
:all)
> +    end
> +    machine_spec[:realm] = instance.realm_id if instance.realm_id
> +    machine_spec[:machine_image] = { :href => context.machine_image_url(instance.image_id)
} if instance.image_id
> +    self.new(context, :values => machine_spec)
> +  end
> +
> +  # FIXME: This will convert 'RUNNING' state to 'STARTED'
> +  # which is defined in CIMI (p65)
> +  #
> +  def self.convert_instance_state(state)
> +    case state
> +      when "RUNNING" then "STARTED"
> +      when "PENDING" then "CREATING" #aruba only exception... could be "STARTING" here
> +      else state
> +    end
> +  end
> +
> +  def self.convert_instance_cpu(profile, context)
> +    cpu_override = profile.overrides.find { |p, v| p == :cpu }
> +    if cpu_override.nil?
> +      CIMI::Model::MachineConfiguration.find(profile.id, context).cpu
> +    else
> +      cpu_override[1]
> +    end
> +  end
> +
> +  def self.convert_instance_memory(profile, context)
> +    machine_conf = CIMI::Model::MachineConfiguration.find(profile.name, context)
> +    memory_override = profile.overrides.find { |p, v| p == :memory }
> +    memory_override.nil? ? machine_conf.memory.to_i : context.to_kibibyte(memory_override[1].to_i,"MB")
> +  end
> +
> +  def self.convert_instance_addresses(instance)
> +    (instance.public_addresses + instance.private_addresses).collect do |address|
> +      {
> +        :hostname => address.is_hostname? ? address : nil,
> +        :mac_address => address.is_mac? ? address : nil,
> +        :state => 'Active',
> +        :protocol => 'IPv4',
> +        :address => address.is_ipv4? ? address : nil,
> +        :allocation => 'Static'
> +      }
> +    end
> +  end
> +
> +  def self.convert_instance_actions(instance, context)
> +    actions = instance.actions.collect do |action|
> +      action = :restart if action == :reboot
> +      name = action
> +      name = :delete if action == :destroy # In CIMI destroy operation become delete
> +      { :href => context.send(:"#{action}_machine_url", instance.id), :rel =>
"http://schemas.dmtf.org/cimi/1/action/#{name}" }
> +    end
> +    actions <<  { :href => context.send(:"machine_images_url"), :rel =>
"http://schemas.dmtf.org/cimi/1/action/capture" } if instance.can_create_image?
> +    actions
> +  end
> +
> +  def self.convert_storage_volumes(instance, context)
> +    instance.storage_volumes ||= [] #deal with nilpointers
> +    instance.storage_volumes.map{|vol| {:href=>context.volume_url(vol.keys.first),
> +                                       :initial_location=>vol.values.first} }
> +  end
> +
> +end
> -- 
> 1.8.1.2
> 

-- 
Michal Fojtik <mfojtik@redhat.com>
Deltacloud API, CloudForms

Mime
View raw message