You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@deltacloud.apache.org by lu...@apache.org on 2013/03/14 19:19:03 UTC

[8/15] git commit: CIMI: split models into model and service objects

CIMI: split models into model and service objects

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.


Project: http://git-wip-us.apache.org/repos/asf/deltacloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/deltacloud/commit/cc6f9198
Tree: http://git-wip-us.apache.org/repos/asf/deltacloud/tree/cc6f9198
Diff: http://git-wip-us.apache.org/repos/asf/deltacloud/diff/cc6f9198

Branch: refs/heads/master
Commit: cc6f919824f1c682a63cc383c1b2f8eb6841480c
Parents: d86e066
Author: David Lutterkort <lu...@redhat.com>
Authored: Thu Feb 28 16:15:27 2013 -0800
Committer: David Lutterkort <lu...@redhat.com>
Committed: Wed Mar 13 17:28:13 2013 -0700

----------------------------------------------------------------------
 server/lib/cimi/collections/base.rb            |    4 +-
 server/lib/cimi/helpers/cimi_helper.rb         |    2 +-
 server/lib/cimi/helpers/database_helper.rb     |    3 +-
 server/lib/cimi/models.rb                      |    9 -
 server/lib/cimi/models/base.rb                 |   74 +-------
 server/lib/cimi/models/collection.rb           |   27 +--
 server/lib/cimi/models/machine.rb              |  125 ------------
 server/lib/cimi/service.rb                     |   28 +++
 server/lib/cimi/service/base.rb                |  201 +++++++++++++++++++
 server/lib/cimi/service/machine.rb             |  147 ++++++++++++++
 server/tests/cimi/collections/common.rb        |   27 +++
 server/tests/cimi/collections/machines_test.rb |    7 +
 server/tests/cimi/db/database_helper_test.rb   |   11 +-
 13 files changed, 430 insertions(+), 235 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/collections/base.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/collections/base.rb b/server/lib/cimi/collections/base.rb
index 1e49ea3..0d1d0ee 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

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/helpers/cimi_helper.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/helpers/cimi_helper.rb b/server/lib/cimi/helpers/cimi_helper.rb
index e23d819..6d9403e 100644
--- a/server/lib/cimi/helpers/cimi_helper.rb
+++ b/server/lib/cimi/helpers/cimi_helper.rb
@@ -72,7 +72,7 @@ module CIMI
     end
 
     def deltacloud_create_method_for(cimi_entity)
-      case cimi_entity
+      case cimi_entity.to_s
         when "machine"                then "create_instance"
         when "machine_configuration"  then "create_hardware_profile"
         when "machine_image"          then "create_image"

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/helpers/database_helper.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/helpers/database_helper.rb b/server/lib/cimi/helpers/database_helper.rb
index 7e5f99a..b62fbbc 100644
--- a/server/lib/cimi/helpers/database_helper.rb
+++ b/server/lib/cimi/helpers/database_helper.rb
@@ -25,8 +25,7 @@ module Deltacloud
         "volume_configuration", "volume_template" ]
 
      def provides?(entity)
-       return true if DATABASE_COLLECTIONS.include? entity
-       return false
+       DATABASE_COLLECTIONS.include? entity.to_s
      end
 
       def current_provider

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/models.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/models.rb b/server/lib/cimi/models.rb
index edfb0e3..8eeb96c 100644
--- a/server/lib/cimi/models.rb
+++ b/server/lib/cimi/models.rb
@@ -37,15 +37,6 @@ end
 
 require 'require_relative' if RUBY_VERSION < '1.9'
 
-# Database entities
-#
-require_relative './../db/provider'
-require_relative './../db/entity'
-require_relative './../db/machine_template'
-require_relative './../db/address_template'
-require_relative './../db/volume_configuration'
-require_relative './../db/volume_template'
-
 require_relative './models/schema'
 require_relative './models/resource'
 require_relative './models/collection'

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/models/base.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/models/base.rb b/server/lib/cimi/models/base.rb
index a207bed..fc8cca8 100644
--- a/server/lib/cimi/models/base.rb
+++ b/server/lib/cimi/models/base.rb
@@ -15,8 +15,6 @@
 
 require 'xmlsimple'
 
-require_relative '../helpers/database_helper'
-
 # The base class for any CIMI object that we either read from a request or
 # write as a response. This class handles serializing/deserializing XML and
 # JSON into a common form.
@@ -109,86 +107,16 @@ module CIMI::Model
 
   class Base < Resource
 
-    # Extend the base model with database methods
-    extend Deltacloud::Helpers::Database
-
     # Extend the base model with the collection handling methods
     extend CIMI::Model::CollectionMethods
 
     # Include methods needed to handle the $select query parameter
     include CIMI::Helpers::SelectBaseMethods
+
     #
     # Common attributes for all resources
     #
     text :id, :name, :description, :created, :updated
     hash_map :property
-
-    def initialize(values = {})
-      super(values)
-      retrieve_entity
-    end
-
-    def []=(a, v)
-      v = super
-      retrieve_entity if a == :id
-      v
-    end
-
-    # Save the common attributes name, description, and properties to the
-    # database
-    def save
-      if @entity
-        @entity.name = name
-        @entity.description = description
-        @entity.properties = 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?
-          self.name = @entity.name
-          self.description = @entity.description
-          self.property ||= {}
-          self.property.merge!(@entity.properties)
-        end
-      else
-        @entity = nil
-      end
-    end
-
   end
 end

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/models/collection.rb
----------------------------------------------------------------------
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)

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/models/machine.rb
----------------------------------------------------------------------
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

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/service.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/service.rb b/server/lib/cimi/service.rb
new file mode 100644
index 0000000..5231c24
--- /dev/null
+++ b/server/lib/cimi/service.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 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 './../db/provider'
+require_relative './../db/entity'
+require_relative './../db/machine_template'
+require_relative './../db/address_template'
+require_relative './../db/volume_configuration'
+require_relative './../db/volume_template'
+
+require_relative './service/base'
+require_relative './service/machine'

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/service/base.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/service/base.rb b/server/lib/cimi/service/base.rb
new file mode 100644
index 0000000..7df57f6
--- /dev/null
+++ b/server/lib/cimi/service/base.rb
@@ -0,0 +1,201 @@
+# 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
+
+    # Lookup a reference and return the corresponding model
+    def resolve(ref)
+      self.class.resolve(ref, context)
+    end
+
+    def self.resolve(ref, ctx)
+      model = nil
+      if ref.href?
+        name = ref.class.superclass.name.split('::').last
+        service_class = CIMI::Service::const_get(name)
+        id = ref.href.split('/').last
+        model = service_class.find(id, ctx)
+      else
+        # FIXME: if ref.href? we need to overwrite
+        # attributes in model with ones from ref as long as they are present
+        model = ref
+      end
+      model
+    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.driver.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
+        before_save
+        @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
+
+    protected
+
+    def attributes_to_copy
+      [:name, :description]
+    end
+
+    def before_save
+      attributes_to_copy.each { |a| @entity[a] = @model[a] }
+      @entity.properties = @model.property
+    end
+
+    def after_retrieve
+      attributes_to_copy.each { |a| @model[a] = @entity[a] }
+      @model.property ||= {}
+      @model.property.merge!(@entity.properties)
+    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(@model)
+        if @entity.exists?
+          after_retrieve
+        end
+      else
+        @entity = nil
+      end
+    end
+
+  end
+end

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/lib/cimi/service/machine.rb
----------------------------------------------------------------------
diff --git a/server/lib/cimi/service/machine.rb b/server/lib/cimi/service/machine.rb
new file mode 100644
index 0000000..72e5346
--- /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 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, &block)
+    begin
+      op = action.operation
+      op = :reboot if op == :restart
+      puts "PERFORM #{op.inspect} on #{ref_id(id)}"
+      if context.driver.send(:"#{op}_instance", context.credentials, ref_id(id))
+        block.callback :success
+      else
+        raise "Operation #{op} failed to execute on given Machine #{ref_id(id)}"
+      end
+    rescue => e
+      raise
+      block.callback :failure, e.message
+    end
+  end
+
+  def self.delete!(id, context)
+    context.driver.destroy_instance(context.credentials, id)
+    new(context, :values => { :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

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/tests/cimi/collections/common.rb
----------------------------------------------------------------------
diff --git a/server/tests/cimi/collections/common.rb b/server/tests/cimi/collections/common.rb
index 5948dde..f44f9cd 100644
--- a/server/tests/cimi/collections/common.rb
+++ b/server/tests/cimi/collections/common.rb
@@ -15,3 +15,30 @@ Deltacloud[:cimi].require!
 Deltacloud[:cimi].default_frontend!
 
 def formats; [ 'application/xml', 'application/json' ]; end
+
+def model_class
+  resource = nil
+  resp = last_response
+  ct = resp.content_type
+  if ct == "application/json"
+    json = JSON::parse(last_response.body)
+    json["resourceURI"].wont_be_nil
+    resource = json["resourceURI"].split("/").last
+  elsif ct == "application/xml"
+    xml = Nokogiri::XML(last_response.body)
+    if xml.root.name == "Collection"
+      resource = xml.root["resourceURI"].split("/").last
+    else
+      resource = xml.root.name
+    end
+  elsif resp.body.nil? || resp.body.size == 0
+    raise "Can not construct model from empty body"
+  else
+    raise "Unexpected content type #{resp.content_type}"
+  end
+  CIMI::Model::const_get(resource)
+end
+
+def model
+  model_class.parse(last_response.body, last_response.content_type)
+end

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/tests/cimi/collections/machines_test.rb
----------------------------------------------------------------------
diff --git a/server/tests/cimi/collections/machines_test.rb b/server/tests/cimi/collections/machines_test.rb
index b2f7efb..5e8346e 100644
--- a/server/tests/cimi/collections/machines_test.rb
+++ b/server/tests/cimi/collections/machines_test.rb
@@ -35,6 +35,13 @@ describe CIMI::Collections::Machines do
     xml.root.name.must_equal 'Machine'
   end
 
+  it 'should have an "add" operation for the machine collection' do
+    get root_url + '/machines'
+    model.operations.wont_be_empty
+    add_ops = model.operations.select { |op| op.rel == "add" }
+    add_ops.size.must_equal 1
+  end
+
   describe "$expand" do
     def machine(*expand)
       url = '/machines/inst1'

http://git-wip-us.apache.org/repos/asf/deltacloud/blob/cc6f9198/server/tests/cimi/db/database_helper_test.rb
----------------------------------------------------------------------
diff --git a/server/tests/cimi/db/database_helper_test.rb b/server/tests/cimi/db/database_helper_test.rb
index b474307..43642ec 100644
--- a/server/tests/cimi/db/database_helper_test.rb
+++ b/server/tests/cimi/db/database_helper_test.rb
@@ -16,7 +16,8 @@ describe Deltacloud::Helpers::Database do
   before do
     @provider = Deltacloud::Database::Provider
     @entity = Deltacloud::Database::Entity
-    @baseModel = CIMI::Model::Base
+    @baseService = CIMI::Service::Base
+    @baseModel = @baseService.model_class
 
     @db = DatabaseHelper.new
     @prov = @provider::lookup
@@ -98,7 +99,7 @@ describe Deltacloud::Helpers::Database do
     check_entity_base_attrs new_entity, @entity, @prov
 
     base = @baseModel.new(:id => 'base1')
-    base.destroy
+    @baseService.new(nil, :model => base).destroy
     entity = @entity.retrieve(base)
     entity.wont_be_nil
     entity.exists?.must_equal false
@@ -123,10 +124,12 @@ describe Deltacloud::Helpers::Database do
   }
 }
     '
-    machine = CIMI::Model::Machine.from_json(json)
+
+    model = CIMI::Model::Machine.from_json(json)
+    machine = CIMI::Service::Machine.new(nil, :model => model)
     machine.save
 
-    m2 = CIMI::Model::Machine.new(:id => machine.id)
+    m2 = CIMI::Service::Machine.new(nil, :values => { :id => machine.id })
     m2.name.must_equal 'myDatabaseMachine'
     m2.description.must_equal 'This is a demo machine'
     m2.property.must_be_kind_of Hash