deltacloud-core 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. data/Rakefile +10 -2
  2. data/bin/deltacloudd +10 -10
  3. data/config.ru +2 -1
  4. data/config/drivers/digitalocean.yaml +3 -0
  5. data/deltacloud-core.gemspec +13 -6
  6. data/lib/cimi/collections.rb +1 -1
  7. data/lib/cimi/collections/address_templates.rb +27 -3
  8. data/lib/cimi/collections/addresses.rb +1 -1
  9. data/lib/cimi/collections/base.rb +1 -0
  10. data/lib/cimi/collections/cloud_entry_point.rb +4 -0
  11. data/lib/cimi/collections/credentials.rb +2 -2
  12. data/lib/cimi/collections/machine_images.rb +20 -0
  13. data/lib/cimi/collections/machine_templates.rb +72 -0
  14. data/lib/cimi/collections/machines.rb +50 -41
  15. data/lib/cimi/collections/network_ports.rb +3 -3
  16. data/lib/cimi/collections/networks.rb +4 -4
  17. data/lib/cimi/collections/resource_metadata.rb +1 -1
  18. data/lib/cimi/collections/volume_configurations.rb +25 -0
  19. data/lib/cimi/collections/volume_images.rb +21 -1
  20. data/lib/cimi/collections/volume_templates.rb +69 -0
  21. data/lib/cimi/collections/volumes.rb +5 -10
  22. data/lib/cimi/dependencies.rb +0 -1
  23. data/lib/cimi/helpers.rb +4 -1
  24. data/lib/cimi/helpers/cimi_helper.rb +62 -0
  25. data/lib/cimi/helpers/database_helper.rb +95 -0
  26. data/lib/cimi/models.rb +15 -1
  27. data/lib/cimi/models/address.rb +10 -5
  28. data/lib/cimi/models/address_template.rb +67 -3
  29. data/lib/cimi/models/base.rb +8 -5
  30. data/lib/cimi/models/cloud_entry_point.rb +6 -1
  31. data/lib/cimi/models/collection.rb +9 -4
  32. data/lib/cimi/models/disk.rb +6 -1
  33. data/lib/cimi/models/errors.rb +8 -0
  34. data/lib/cimi/models/machine.rb +68 -42
  35. data/lib/cimi/models/machine_configuration.rb +2 -2
  36. data/lib/cimi/models/machine_image.rb +41 -6
  37. data/lib/cimi/models/machine_template.rb +58 -0
  38. data/lib/cimi/models/machine_volume.rb +51 -3
  39. data/lib/cimi/models/resource_metadata.rb +88 -25
  40. data/lib/cimi/models/schema.rb +19 -5
  41. data/lib/cimi/models/volume.rb +66 -30
  42. data/lib/cimi/models/volume_configuration.rb +53 -21
  43. data/lib/cimi/models/volume_image.rb +24 -9
  44. data/lib/cimi/models/volume_template.rb +61 -0
  45. data/lib/cimi/server.rb +0 -6
  46. data/lib/db.rb +82 -0
  47. data/lib/db/address_template.rb +15 -0
  48. data/lib/db/entity.rb +22 -0
  49. data/lib/db/machine_template.rb +10 -0
  50. data/lib/db/provider.rb +13 -0
  51. data/lib/db/volume_configuration.rb +10 -0
  52. data/lib/db/volume_template.rb +10 -0
  53. data/lib/deltacloud/collections/addresses.rb +1 -1
  54. data/lib/deltacloud/collections/base.rb +12 -1
  55. data/lib/deltacloud/collections/buckets.rb +41 -8
  56. data/lib/deltacloud/collections/drivers.rb +1 -1
  57. data/lib/deltacloud/collections/firewalls.rb +2 -2
  58. data/lib/deltacloud/collections/images.rb +1 -1
  59. data/lib/deltacloud/collections/instances.rb +7 -1
  60. data/lib/deltacloud/collections/keys.rb +1 -1
  61. data/lib/deltacloud/collections/load_balancers.rb +4 -4
  62. data/lib/deltacloud/collections/storage_snapshots.rb +5 -1
  63. data/lib/deltacloud/collections/storage_volumes.rb +7 -3
  64. data/lib/deltacloud/drivers/base_driver.rb +10 -9
  65. data/lib/deltacloud/drivers/cimi_features.rb +42 -0
  66. data/lib/deltacloud/drivers/digitalocean/digitalocean_driver.rb +307 -0
  67. data/lib/deltacloud/drivers/ec2/ec2_driver.rb +40 -14
  68. data/lib/deltacloud/drivers/exceptions.rb +8 -0
  69. data/lib/deltacloud/drivers/features.rb +19 -2
  70. data/lib/deltacloud/drivers/fgcp/fgcp_client.rb +11 -0
  71. data/lib/deltacloud/drivers/fgcp/fgcp_driver.rb +83 -11
  72. data/lib/deltacloud/drivers/gogrid/gogrid_client.rb +1 -1
  73. data/lib/deltacloud/drivers/mock/mock_client.rb +2 -4
  74. data/lib/deltacloud/drivers/mock/mock_driver.rb +29 -0
  75. data/lib/deltacloud/drivers/openstack/openstack_driver.rb +153 -36
  76. data/lib/deltacloud/drivers/rackspace/anti_cache_monkey_patch.rb +20 -0
  77. data/lib/deltacloud/drivers/rackspace/rackspace_driver.rb +1 -0
  78. data/lib/deltacloud/drivers/rhevm/rhevm_driver.rb +30 -12
  79. data/lib/deltacloud/drivers/sbc/sbc_client.rb +0 -1
  80. data/lib/deltacloud/drivers/sbc/sbc_driver.rb +5 -1
  81. data/lib/deltacloud/drivers/vsphere/vsphere_client.rb +1 -1
  82. data/lib/deltacloud/drivers/vsphere/vsphere_driver.rb +19 -9
  83. data/lib/deltacloud/helpers/blob_stream_helper.rb +42 -3
  84. data/lib/deltacloud/helpers/deltacloud_helper.rb +31 -14
  85. data/lib/deltacloud/models/address.rb +9 -0
  86. data/lib/deltacloud/models/base_model.rb +4 -0
  87. data/lib/deltacloud/models/blob.rb +12 -0
  88. data/lib/deltacloud/models/bucket.rb +9 -0
  89. data/lib/deltacloud/models/firewall.rb +13 -1
  90. data/lib/deltacloud/models/firewall_rule.rb +14 -0
  91. data/lib/deltacloud/models/hardware_profile.rb +14 -0
  92. data/lib/deltacloud/models/image.rb +15 -0
  93. data/lib/deltacloud/models/instance.rb +40 -1
  94. data/lib/deltacloud/models/instance_address.rb +9 -0
  95. data/lib/deltacloud/models/key.rb +15 -0
  96. data/lib/deltacloud/models/load_balancer.rb +20 -0
  97. data/lib/deltacloud/models/metric.rb +15 -0
  98. data/lib/deltacloud/models/provider.rb +8 -0
  99. data/lib/deltacloud/models/realm.rb +9 -0
  100. data/lib/deltacloud/models/state_machine.rb +8 -0
  101. data/lib/deltacloud/models/storage_snapshot.rb +11 -0
  102. data/lib/deltacloud/models/storage_volume.rb +24 -0
  103. data/lib/deltacloud/server.rb +1 -3
  104. data/lib/deltacloud/version.rb +2 -1
  105. data/lib/deltacloud_rack.rb +1 -1
  106. data/tests/cimi/db/database_helper_test.rb +190 -0
  107. data/tests/cimi/db/db_helper.rb +30 -0
  108. data/tests/cimi/db/schema_test.rb +94 -0
  109. data/tests/cimi/model/collection_spec.rb +0 -1
  110. data/tests/cimi/model/machine_spec.rb +17 -0
  111. data/tests/cimi/spec_helper.rb +3 -2
  112. data/tests/deltacloud/collections/buckets_collection_test.rb +3 -0
  113. data/tests/deltacloud/collections/drivers_collection_test.rb +10 -0
  114. data/tests/deltacloud/collections/hardware_profiles_collection_test.rb +4 -0
  115. data/tests/deltacloud/collections/images_collection_test.rb +4 -0
  116. data/tests/deltacloud/collections/instances_collection_test.rb +14 -1
  117. data/tests/deltacloud/collections/keys_collection_test.rb +4 -0
  118. data/tests/deltacloud/collections/realms_collection_test.rb +47 -0
  119. data/tests/deltacloud/collections/storage_snapshots_collection_test.rb +47 -0
  120. data/tests/deltacloud/collections/storage_volumes_collection_test.rb +47 -0
  121. data/tests/deltacloud/common.rb +15 -0
  122. data/tests/deltacloud/deltacloud_helper_test.rb +0 -4
  123. data/tests/deltacloud/launcher_test.rb +108 -0
  124. data/tests/drivers/ec2/buckets_test.rb +2 -1
  125. data/tests/drivers/mock/buckets_test.rb +27 -0
  126. data/tests/drivers/mock/instances_test.rb +6 -0
  127. data/tests/drivers/openstack/common.rb +1 -1
  128. data/tests/drivers/openstack/hardware_profiles_test.rb +2 -1
  129. data/tests/drivers/openstack/instances_test.rb +18 -17
  130. data/tests/drivers/rhevm/common.rb +1 -0
  131. data/tests/drivers/rhevm/images_test.rb +1 -1
  132. data/tests/drivers/rhevm/instance_test.rb +7 -7
  133. data/tests/helpers/rack/rack_matrix_params_test.rb +0 -2
  134. data/tests/test_helper.rb +2 -1
  135. data/views/errors/403.xml.haml +6 -0
  136. data/views/errors/409.html.haml +47 -0
  137. data/views/errors/409.xml.haml +11 -0
  138. data/views/errors/502.html.haml +6 -5
  139. data/views/images/show.xml.haml +3 -2
  140. data/views/instances/new.html.haml +1 -1
  141. metadata +77 -30
@@ -17,8 +17,10 @@ class CIMI::Model::MachineImage < CIMI::Model::Base
17
17
 
18
18
  acts_as_root_entity
19
19
 
20
- href :image_location
21
- text :image_data
20
+ text :state
21
+ text :type
22
+ text :image_location
23
+ href :related_image
22
24
 
23
25
  array :operations do
24
26
  scalar :rel, :href
@@ -36,13 +38,46 @@ class CIMI::Model::MachineImage < CIMI::Model::Base
36
38
  end
37
39
 
38
40
  def self.from_image(image, context)
41
+ stored_attributes = load_attributes_for(image)
39
42
  self.new(
40
- :name => image.id,
41
43
  :id => context.machine_image_url(image.id),
42
- :description => image.description,
43
- :created => Time.now.xmlschema,
44
- :image_location => { :href => "#{context.driver.name}://#{image.id}" } # FIXME
44
+ :name => stored_attributes[:name] || image.id,
45
+ :description => stored_attributes[:description] || image.description,
46
+ :state => image.state || 'UNKNOWN',
47
+ :type => "IMAGE",
48
+ :created => image.creation_time.nil? ? Time.now.xmlschema : Time.parse(image.creation_time.to_s).xmlschema,
49
+ :image_location => (stored_attributes[:property] && stored_attributes[:property]['image_location']) ?
50
+ stored_attributes[:property].delete('image_location') : 'unknown',
51
+ :property => stored_attributes[:property]
45
52
  )
46
53
  end
47
54
 
55
+ def self.create(request_body, context)
56
+ # The 'imageLocation' attribute is mandatory in CIMI, however in Deltacloud
57
+ # there is no way how to figure out from what Machine the MachineImage was
58
+ # created from. For that we need to store this attribute in properties.
59
+ #
60
+ if context.grab_content_type(context.request.content_type, request_body) == :xml
61
+ input = XmlSimple.xml_in(request_body.read, {"ForceArray"=>false,"NormaliseSpace"=>2})
62
+ raise 'imageLocation attribute is mandatory' unless input['imageLocation']
63
+ input['property'] ||= {}
64
+ input['property'].kind_of?(Array) ?
65
+ input['property'] << { 'image_location' => input['imageLocation'] } : input['property'].merge!('image_location' => input['imageLocation'])
66
+ else
67
+ input = JSON.parse(request_body.read)
68
+ raise 'imageLocation attribute is mandatory' unless input['imageLocation']
69
+ input['properties'] ||= []
70
+ input['properties'] << { 'image_location' => input['imageLocation'] }
71
+ end
72
+ params = {:id => context.href_id(input["imageLocation"], :machines), :name=>input["name"], :description=>input["description"]}
73
+ image = context.driver.create_image(context.credentials, params)
74
+ store_attributes_for(image, input)
75
+ from_image(image, context)
76
+ end
77
+
78
+ def self.delete!(image_id, context)
79
+ context.driver.destroy_image(context.credentials, image_id)
80
+ delete_attributes_for(::Image.new(:id => image_id))
81
+ end
82
+
48
83
  end
@@ -40,4 +40,62 @@ class CIMI::Model::MachineTemplate < CIMI::Model::Base
40
40
  array :operations do
41
41
  scalar :rel, :href
42
42
  end
43
+
44
+ class << self
45
+ def find(id, context)
46
+ if id == :all
47
+ current_db.machine_templates.map { |t| from_db(t, context) }
48
+ else
49
+ template = current_db.machine_templates_dataset.first(:id => id)
50
+ raise CIMI::Model::NotFound unless template
51
+ from_db(template, context)
52
+ end
53
+ end
54
+
55
+ def create_from_json(body, context)
56
+ json = JSON.parse(body)
57
+ new_template = current_db.add_machine_template(
58
+ :name => json['name'],
59
+ :description => json['description'],
60
+ :machine_config => json['machineConfig']['href'],
61
+ :machine_image => json['machineImage']['href'],
62
+ :ent_properties => json['properties'].to_json
63
+ )
64
+ from_db(new_template, context)
65
+ end
66
+
67
+ def create_from_xml(body, context)
68
+ xml = XmlSimple.xml_in(body)
69
+ new_template = current_db.add_machine_template(
70
+ :name => xml['name'].first,
71
+ :description => xml['description'].first,
72
+ :machine_config => xml['machineConfig'].first['href'],
73
+ :machine_image => xml['machineImage'].first['href'],
74
+ :ent_properties => JSON::dump(xml['property'].inject({}) { |r, p| r[p['key']]=p['content']; r })
75
+ )
76
+ from_db(new_template, context)
77
+ end
78
+
79
+ def delete!(id, context)
80
+ current_db.machine_templates.first(:id => id).destroy
81
+ end
82
+
83
+ private
84
+
85
+ def from_db(model, context)
86
+ self.new(
87
+ :id => context.machine_template_url(model.id),
88
+ :name => model.name,
89
+ :description => model.description,
90
+ :machine_config => { :href => model.machine_config },
91
+ :machine_image => { :href => model.machine_image },
92
+ :property => JSON::parse(model.ent_properties),
93
+ :created => Time.parse(model.created_at.to_s).xmlschema,
94
+ :operations => [
95
+ { :href => context.destroy_machine_template_url(model.id), :rel => 'http://schemas.dmtf.org/cimi/1/action/delete' }
96
+ ]
97
+ )
98
+ end
99
+ end
100
+
43
101
  end
@@ -26,17 +26,65 @@ class CIMI::Model::MachineVolume < CIMI::Model::Base
26
26
  if id == :all
27
27
  volumes = context.driver.storage_volumes(context.credentials)
28
28
  volumes.inject([]) do |attached, vol|
29
+ id = context.machine_url(instance_id)+"/volumes/#{vol.id}"
29
30
  attached << self.new(
30
- :id => context.machine_url(instance_id)+"/volumes/#{vol.id}",
31
+ :id => id,
31
32
  :name => vol.id,
32
33
  :description => "MachineVolume #{vol.id} for Machine #{instance_id}",
33
- :created => Time.parse(vol.created).xmlschema,
34
+ :created => vol.created.nil? ? nil : Time.parse(vol.created).xmlschema,
34
35
  :initial_location => vol.device,
35
- :volume => {:href=>context.volume_url(vol.id)}
36
+ :volume => {:href=>context.volume_url(vol.id)},
37
+ :operations => [{:href=>id, :rel => "delete" }]
36
38
  ) if vol.instance_id == instance_id
37
39
  attached
38
40
  end
39
41
  else
42
+ vol = context.driver.storage_volume(context.credentials, {:id=>id})
43
+ id = context.machine_url(instance_id)+"/volumes/#{vol.id}"
44
+ raise CIMI::Model::NotFound unless vol.instance_id == instance_id
45
+ self.new(
46
+ :id => id,
47
+ :name => vol.id,
48
+ :description => "MachineVolume #{vol.id} for Machine #{instance_id}",
49
+ :created => vol.created.nil? ? nil : Time.parse(vol.created).xmlschema,
50
+ :initial_location => vol.device,
51
+ :volume => {:href=>context.volume_url(vol.id)},
52
+ :operations => [{:href=>id, :rel => "delete" }]
53
+ )
40
54
  end
41
55
  end
56
+
57
+ def self.find_to_attach_from_xml(xml_in, context)
58
+ xml = XmlSimple.xml_in(xml_in)
59
+ vol_id = xml["volume"].first["href"].split("/").last
60
+ location = xml["initialLocation"].first.strip
61
+ [vol_id, location]
62
+ end
63
+
64
+ def self.find_to_attach_from_json(json_in, context)
65
+ json = JSON.parse(json_in)
66
+ vol_id = json["volume"]["href"].split("/").last
67
+ location = json["initialLocation"]
68
+ [vol_id, location]
69
+ end
70
+
71
+
72
+ def self.collection_for_instance(instance_id, context)
73
+ machine_volumes = self.find(instance_id, context)
74
+ volumes_url = context.url("/machines/#{instance_id}/volumes")
75
+ unless CIMI::Model.const_defined?('MachineVolumeCollection')
76
+ collection_class = CIMI::Model::Collection.generate(self)
77
+ else
78
+ collection_class = CIMI::Model::MachineVolumeCollection
79
+ end
80
+ collection_class.new(
81
+ :id => volumes_url,
82
+ :name => 'default',
83
+ :count => machine_volumes.size,
84
+ :description => "Volume collection for Machine #{instance_id}",
85
+ :entries => machine_volumes,
86
+ :operations => [{ :href => volumes_url.singularize+"_attach", :rel => "add" }]
87
+ )
88
+ end
89
+
42
90
  end
@@ -16,7 +16,7 @@
16
16
 
17
17
  class CIMI::Model::ResourceMetadata < CIMI::Model::Base
18
18
 
19
- acts_as_root_entity
19
+ text :name
20
20
 
21
21
  text :type_uri
22
22
 
@@ -25,10 +25,20 @@ class CIMI::Model::ResourceMetadata < CIMI::Model::Base
25
25
  scalar :namespace
26
26
  scalar :type
27
27
  scalar :required
28
- scalar :constraints
28
+ array :constraints do
29
+ text :value
30
+ end
29
31
  end
30
32
 
31
- array :operations do
33
+ array :capabilities do
34
+ scalar :name
35
+ scalar :uri
36
+ scalar :description
37
+ scalar :value, :text => :direct
38
+ end
39
+
40
+
41
+ array :actions do
32
42
  scalar :name
33
43
  scalar :uri
34
44
  scalar :description
@@ -37,49 +47,102 @@ class CIMI::Model::ResourceMetadata < CIMI::Model::Base
37
47
  scalar :output_message
38
48
  end
39
49
 
50
+ array :operations do
51
+ scalar :rel, :href
52
+ end
53
+
40
54
  def self.find(id, context)
41
- resource_metadata = []
42
55
  if id == :all
56
+ resource_metadata = []
43
57
  CIMI::Model.root_entities.each do |resource_class|
44
- resource_metadata << resource_class.create_resource_metadata(context) if resource_class.respond_to?(:create_resource_metadata)
58
+ meta = resource_metadata_for(resource_class, context)
59
+ resource_metadata << meta unless none_defined(meta)
45
60
  end
46
61
  return resource_metadata
47
62
  else
48
63
  resource_class = CIMI::Model.const_get("#{id.camelize}")
49
- if resource_class.respond_to?(:create_resource_metadata)
50
- resource_class.create_resource_metadata(context)
51
- end
64
+ resource_metadata_for(resource_class, context)
52
65
  end
53
66
  end
54
67
 
55
- def self.metadata_from_deltacloud_features(cimi_resource, dcloud_resource, context)
56
- deltacloud_features = context.driver.class.features[dcloud_resource]
57
- metadata_attributes = deltacloud_features.map{|f| attributes_from_feature(f)}
58
- from_feature(cimi_resource, context, metadata_attributes.flatten!)
68
+ def self.resource_metadata_for(resource_class, context)
69
+ attributes = rm_attributes_for(resource_class, context)
70
+ capabilities = rm_capabilities_for(resource_class, context)
71
+ actions = rm_actions_for(resource_class, context)
72
+ cimi_resource = resource_class.name.split("::").last
73
+ self.new({ :id => context.resource_metadata_url(cimi_resource.underscore),
74
+ :name => cimi_resource,
75
+ :type_uri => resource_class.resource_uri,
76
+ :attributes => attributes,
77
+ :capabilities => capabilities,
78
+ :actions => actions
79
+ })
59
80
  end
60
81
 
61
- def includes_attribute?(attribute)
62
- self.attributes.any?{|attr| attr[:name] == attribute}
82
+ def self.resource_attributes
83
+ @resource_attributes ||= {}
84
+ end
85
+
86
+ def self.add_resource_attribute!(klass, name, opts={})
87
+ resource_attributes[klass.name] ||= {}
88
+ resource_attributes[klass.name][name] = opts
63
89
  end
64
90
 
65
91
  private
66
92
 
67
- def self.attributes_from_feature(feature)
68
- feature = CIMI::FakeCollection.feature(feature)
69
- feature.operations.first.params_array.map do |p|
93
+ def self.rm_attributes_for(resource_class, context)
94
+ return [] if resource_attributes[resource_class.name].nil?
95
+ resource_attributes[resource_class.name].map do |attr_name, attr_def|
96
+ if attr_def.has_key? :constraints
97
+ constraints = attr_def[:constraints].call(context)
98
+ else
99
+ constraints = []
100
+ end
70
101
  {
71
- :name=> p.name,
72
- :type=> "xs:string",
73
- :required=> p.required? ? "true" : "false",
74
- :constraints=> (feature.constraints.empty? ? (feature.description.nil? ? "" : feature.description): feature.constraints)
102
+ :name => attr_name.to_s,
103
+ # TODO: We need to make this URI return description of this 'non-CIMI'
104
+ # attribute
105
+ :namespace => "http://deltacloud.org/cimi/#{resource_class.name.split('::').last}/#{attr_name}",
106
+ :type => translate_attr_type(attr_def[:type]),
107
+ :required => attr_def[:required] ? 'true' : 'false',
108
+ :constraints => constraints.map { |v| { :value => v }}
75
109
  }
76
110
  end
77
111
  end
78
112
 
79
- def self.from_feature(cimi_resource, context, metadata_attributes)
80
- self.new(:name => cimi_resource, :uri=>"#{context.resource_metadata_url}/#{cimi_resource.underscore}",
81
- :type_uri=> context.send("#{cimi_resource.pluralize.underscore}_url"),
82
- :attributes => metadata_attributes)
113
+ def self.rm_capabilities_for(resource_class,context)
114
+ cimi_object = resource_class.name.split("::").last.underscore.pluralize.to_sym
115
+ capabilities = (context.driver.class.features[cimi_object] || []).inject([]) do |res, cur|
116
+ feat = CIMI::FakeCollection.feature(cur)
117
+ values = (context.driver.class.constraints[cimi_object][feat.name][:values] || []).inject([]) do |vals, val|
118
+ vals << val
119
+ vals
120
+ end
121
+ res << {:name => feat.name.to_s.camelize,
122
+ :uri => CMWG_NAMESPACE+"/capability/#{cimi_object.to_s.camelize.singularize}/#{feat.name.to_s.camelize}",
123
+ :description => feat.description,
124
+ :value => values.join(",") }
125
+ res
126
+ end
127
+ #cimi_resource.underscore.pluralize.to_sym
128
+ end
129
+
130
+ def self.rm_actions_for(resource_class, context)
131
+ []
132
+ end
133
+
134
+ def self.translate_attr_type(type)
135
+ case type
136
+ when :href then 'URI'
137
+ when :text then 'string'
138
+ when :boolean then 'boolean'
139
+ else 'text'
140
+ end
141
+ end
142
+
143
+ def self.none_defined(metadata)
144
+ return true if metadata.capabilities.empty? && metadata.capabilities.empty? && metadata.attributes.empty?
145
+ return false
83
146
  end
84
147
 
85
148
  end
@@ -136,7 +136,7 @@ class CIMI::Model::Schema
136
136
  end
137
137
 
138
138
  def convert_to_xml(model)
139
- xml = {}
139
+ xml = OrderedHash.new
140
140
  @schema.to_xml(model, xml)
141
141
  xml
142
142
  end
@@ -195,7 +195,7 @@ class CIMI::Model::Schema
195
195
 
196
196
  def from_xml(xml, model)
197
197
  model[name] = (xml[xml_name] || []).inject({}) do |result, item|
198
- result[item["name"]] = item["content"]
198
+ result[item["key"]] = item["content"]
199
199
  result
200
200
  end
201
201
  end
@@ -205,7 +205,7 @@ class CIMI::Model::Schema
205
205
  end
206
206
 
207
207
  def to_xml(model, xml)
208
- ary = (model[name] || {}).map { |k, v| { "name" => k, "content" => v } }
208
+ ary = (model[name] || {}).map { |k, v| { "key" => k, "content" => v } }
209
209
  xml[xml_name] = ary unless ary.empty?
210
210
  end
211
211
 
@@ -229,11 +229,15 @@ class CIMI::Model::Schema
229
229
  end
230
230
 
231
231
  def from_xml(xml, model)
232
- model[name] = @collection_class.schema.from_xml(xml[xml_name].first, {})
232
+ if xml[xml_name]
233
+ model[name] = @collection_class.schema.from_xml(xml[xml_name].first, {})
234
+ end
233
235
  end
234
236
 
235
237
  def from_json(json, model)
236
- model[name] = @collection_class.schema.from_json(json[json_name], {})
238
+ if json[json_name]
239
+ model[name] = @collection_class.schema.from_json(json[json_name], {})
240
+ end
237
241
  end
238
242
 
239
243
  def to_xml(model, xml)
@@ -330,6 +334,16 @@ class CIMI::Model::Schema
330
334
  # Requires that the class into which this is included has a
331
335
  # +add_attributes!+ method
332
336
  module DSL
337
+
338
+ def resource_attr(name, opts={})
339
+ CIMI::Model::ResourceMetadata.add_resource_attribute!(self, name, opts)
340
+ if opts[:type]
341
+ send(opts[:type], name)
342
+ else
343
+ text name
344
+ end
345
+ end
346
+
333
347
  def href(*args)
334
348
  opts = args.extract_opts!
335
349
  args.each { |arg| struct(arg, opts) { scalar :href } }
@@ -38,34 +38,40 @@ class CIMI::Model::Volume < CIMI::Model::Base
38
38
  end
39
39
 
40
40
  def self.find(id, context)
41
- volumes = []
42
- opts = ( id == :all ) ? {} : { :id => id }
43
- volumes = context.driver.storage_volumes(context.credentials, opts)
44
- volumes.collect!{ |volume| from_storage_volume(volume, context) }
45
- return volumes.first unless volumes.length > 1 || volumes.length == 0
46
- return volumes
41
+ creds = context.credentials
42
+ if id == :all
43
+ volumes = context.driver.storage_volumes(creds)
44
+ volumes.collect{ |volume| from_storage_volume(volume, context) }
45
+ else
46
+ volume = context.driver.storage_volumes(creds, :id => id).first
47
+ raise CIMI::Model::NotFound unless volume
48
+ from_storage_volume(volume, context)
49
+ end
47
50
  end
48
51
 
49
52
  def self.all(context); find(:all, context); end
50
53
 
51
- def self.create_from_json(json_in, context)
52
- json = JSON.parse(json_in)
53
- volume_config_id = json["volumeTemplate"]["volumeConfig"]["href"].split("/").last
54
- volume_image_id = (json["volumeTemplate"].has_key?("volumeImage") ?
55
- json["volumeTemplate"]["volumeImage"]["href"].split("/").last : nil)
56
- create_volume({:volume_config_id=>volume_config_id, :volume_image_id=>volume_image_id}, context)
57
- end
58
-
59
- def self.create_from_xml(xml_in, context)
60
- xml = XmlSimple.xml_in(xml_in)
61
- volume_config_id = xml["volumeTemplate"][0]["volumeConfig"][0]["href"].split("/").last
62
- volume_image_id = (xml["volumeTemplate"][0].has_key?("volumeImage") ?
63
- xml["volumeTemplate"][0]["volumeImage"][0]["href"].split("/").last : nil)
64
- create_volume({:volume_config_id=>volume_config_id, :volume_image_id=>volume_image_id}, context)
54
+ def self.create(request_body, context, type)
55
+ #json = JSON.parse(json_in)
56
+ input = (type == :xml)? XmlSimple.xml_in(request_body, {"ForceArray"=>false,"NormaliseSpace"=>2}) : JSON.parse(request_body)
57
+ if input["volumeTemplate"]["href"] #template by reference
58
+ #FIXME - don't have volumeTemplates yet - datamapper volume_config =
59
+ else #template by value
60
+ volume_image_id = (input["volumeTemplate"].has_key?("volumeImage") ?
61
+ input["volumeTemplate"]["volumeImage"]["href"].split("/").last : nil)
62
+ if input["volumeTemplate"]["volumeConfig"]["href"] #with config by reference
63
+ volume_config_id = input["volumeTemplate"]["volumeConfig"]["href"].split("/").last
64
+ create_volume({:volume_config_id=>volume_config_id, :volume_image_id=>volume_image_id}, input, context)
65
+ else #with config by value
66
+ capacity = input["volumeTemplate"]["volumeConfig"]["capacity"]
67
+ create_volume({:capacity=>capacity, :volume_image_id=>volume_image_id}, input, context)
68
+ end
69
+ end
65
70
  end
66
71
 
67
72
  def self.delete!(id, context)
68
73
  context.driver.destroy_storage_volume(context.credentials, {:id=>id} )
74
+ delete_attributes_for(StorageVolume.new(:id => id))
69
75
  end
70
76
 
71
77
  def self.find_to_attach_from_json(json_in, context)
@@ -80,27 +86,57 @@ class CIMI::Model::Volume < CIMI::Model::Base
80
86
  :attachment_point=>v["attachmentPoint"] }}
81
87
  end
82
88
 
89
+ def to_entity
90
+ 'volume'
91
+ end
92
+
83
93
  private
84
94
 
85
- def self.create_volume(params, context)
86
- volume_config = CIMI::Model::VolumeConfiguration.find(params[:volume_config_id], context)
87
- opts = {:capacity=>volume_config.capacity[:quantity], :snapshot_id=>params[:volume_image_id] }
95
+ def self.create_volume(params, data, context)
96
+ if params[:volume_config_id]
97
+ volume_config = CIMI::Model::VolumeConfiguration.find(params[:volume_config_id], context)
98
+ opts = {:capacity=>context.from_kibibyte(volume_config.capacity, "GB"),
99
+ :snapshot_id=>params[:volume_image_id],
100
+ :name=>data["name"]}
101
+ elsif params[:capacity]
102
+ opts = {:capacity=>context.from_kibibyte(params[:capacity], "GB"),
103
+ :snapshot_id=>params[:volume_image_id],
104
+ :name=>data["name"]}
105
+ end
88
106
  storage_volume = context.driver.create_storage_volume(context.credentials, opts)
89
- from_storage_volume(storage_volume, context)
107
+ entity = store_attributes_for(storage_volume, data)
108
+ from_storage_volume(storage_volume, context, entity.to_hash)
90
109
  end
91
110
 
92
- def self.from_storage_volume(volume, context)
93
- self.new( { :name => volume.id,
94
- :description => volume.id,
95
- :created => Time.parse(volume.created).xmlschema,
111
+ def self.from_storage_volume(volume, context, stored_attributes=nil)
112
+ stored_attributes ||= load_attributes_for(volume)
113
+ self.new( { :name => stored_attributes[:name] || volume.id,
114
+ :description => stored_attributes[:description] || 'Description of Volume',
115
+ :property => stored_attributes[:property],
116
+ :created => volume.created.nil? ? nil : Time.parse(volume.created).xmlschema,
96
117
  :id => context.volume_url(volume.id),
97
- :capacity => { :quantity=>volume.capacity, :units=>"gibibyte" }, #FIXME... units will vary
118
+ :capacity => context.to_kibibyte(volume.capacity, 'GB'),
98
119
  :bootable => "false", #fixme ... will vary... ec2 doesn't expose this
99
120
  :snapshots => [], #fixme...
100
121
  :type => 'http://schemas.dmtf.org/cimi/1/mapped',
101
122
  :state => volume.state,
102
- :meters => []
123
+ :meters => [],
124
+ :operations => [{:href=> context.volume_url(volume.id), :rel => "delete"}]
103
125
  } )
104
126
  end
105
127
 
128
+ def self.collection_for_instance(instance_id, context)
129
+ instance = context.driver.instance(context.credentials, :id => instance_id)
130
+ volumes = instance.storage_volumes.map do |mappings|
131
+ mappings.keys.map { |volume_id| from_storage_volume(context.driver.storage_volume(context.credentials, :id => volume_id), context) }
132
+ end.flatten
133
+ CIMI::Model::VolumeCollection.new(
134
+ :id => context.url("/machines/#{instance_id}/volumes"),
135
+ :name => 'default',
136
+ :count => volumes.size,
137
+ :description => "Volume collection for Machine #{instance_id}",
138
+ :entries => volumes
139
+ )
140
+ end
141
+
106
142
  end