From: David Lutterkort <[email protected]>
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