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. --- 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 cf92143..f268080 100644 --- a/server/lib/cimi/collections/base.rb +++ b/server/lib/cimi/collections/base.rb @@ -13,14 +13,14 @@ # 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 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