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, mfoj...@redhat.com wrote: > From: David Lutterkort <lut...@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 <mfoj...@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 <mfoj...@redhat.com> Deltacloud API, CloudForms