blob: 695c03fbf2c1f028fb8f54245c1f3acc05179597 [file] [log] [blame]
# 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_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
def parse(context)
req = context.request
model = model_class.parse(req.body, req.content_type)
new(context, :model => model)
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)
if model_class == CIMI::Model::System
params[:system] = id
params[:import_url] = import_url(ctx)
elsif model_class == CIMI::Model::SystemTemplate
params[:import_url] = import_url(ctx)
end
model_class.list(id, entries, params).select_by(ctx.params['$select']).filter_by(ctx.params['$filter'])
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
# used for system and system template import
def self.import_url(ctx)
if ctx.driver.respond_to?("import_#{model_name}") || provides?(model_name)
base_url = ctx.send("#{model_name}_url")
"#{base_url}/import"
end
end
#
# Resource metadata
#
METADATA_TYPES = [ 'text', 'URI', 'string', 'boolean' ]
# A hash of the attributes that need to be mentioned in the given
# context
def self.resource_attributes(context)
metadata.keys.map do |k|
a = model_class.schema.attributes.find { |a| a.name == k }
raise "No attribute named #{k} defined" unless a
constr = metadata[k][:constraints].call(context)
{
:name => a.name,
:namespace => "http://deltacloud.org/cimi/#{model_name}/#{a.name}",
:type => metadata[k][:type],
:required => a.required? ? 'true' : 'false',
:constraints => constr.map { |v| { :value => v } }
}
end
end
def self.resource_capabilities(context)
cimi_object = model_name.to_s.pluralize.to_sym
driver_class = context.driver.class
(driver_class.features[cimi_object] || []).map do |cur|
feat = CIMI::FakeCollection.feature(cur)
values = driver_class.constraints[cimi_object][feat.name][:values] || []
{ :name => feat.name.to_s.camelize,
:uri => CMWG_NAMESPACE+"/capability/#{cimi_object.to_s.camelize.singularize}/#{feat.name.to_s.camelize}",
:description => feat.description,
:value => values.join(",")
}
end
end
# Define the metadata for an attribute; +opts+ must be a Hash that can
# contain the following entries:
# :type : one of METADATA_TYPES
# :constraints : a proc that is passed the current context and
# must return a list of values
def self.metadata(attr_name = nil, opts = nil)
@metadata ||= {}
return @metadata if attr_name.nil? && opts.nil?
opts[:type] ||= 'text'
opts[:type] = opts[:type].to_s
opts[:constraints] ||= lambda { |_| [] }
unless METADATA_TYPES.include?(opts[:type])
raise "Metadata type must be one of #{METADATA_TYPES.join(",")}"
end
metadata[attr_name] = opts
end
#
# Database interactions
#
# 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