tim 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/Gemfile +17 -0
  2. data/README.rdoc +231 -8
  3. data/app/controllers/tim/application_controller.rb +6 -3
  4. data/app/controllers/tim/base_images_controller.rb +10 -18
  5. data/app/controllers/tim/image_versions_controller.rb +10 -9
  6. data/app/controllers/tim/provider_images_controller.rb +13 -10
  7. data/app/controllers/tim/target_images_controller.rb +9 -7
  8. data/app/controllers/tim/templates_controller.rb +7 -7
  9. data/app/exceptions/tim/exceptions.rb +6 -0
  10. data/app/filters/tim/resource_link_filter.rb +56 -0
  11. data/app/models/tim/base_image.rb +7 -3
  12. data/app/models/tim/image_version.rb +4 -2
  13. data/app/models/tim/provider_image.rb +42 -6
  14. data/app/models/tim/target_image.rb +48 -9
  15. data/app/models/tim/template.rb +18 -3
  16. data/app/patches/rails/active_record/autosave_association.rb +29 -0
  17. data/app/validators/template_validator.rb +9 -5
  18. data/app/views/tim/base_images/_base_image.xml.haml +1 -0
  19. data/app/views/tim/target_images/_target_image.xml.haml +1 -0
  20. data/db/migrate/20121115151914_add_import_to_tim_base_images.rb +5 -0
  21. data/db/migrate/20121210131423_add_build_method_to_target_image.rb +5 -0
  22. data/db/migrate/20121216131814_rename_provider_account_id_attribute.rb +13 -0
  23. data/db/migrate/20121216134538_add_factory_base_image_id_to_image_versions.rb +5 -0
  24. data/lib/image_factory/model/base_image.rb +6 -0
  25. data/lib/tim/engine.rb +12 -0
  26. data/lib/tim/version.rb +1 -1
  27. data/spec/controllers/base_images_controller_spec.rb +24 -8
  28. data/spec/controllers/image_versions_controller_spec.rb +20 -7
  29. data/spec/controllers/provider_images_controller_spec.rb +19 -6
  30. data/spec/controllers/target_images_controller_spec.rb +22 -8
  31. data/spec/factories/tim/base_image.rb +5 -1
  32. data/spec/factories/tim/image_factory/target_image.rb +1 -0
  33. data/spec/factories/tim/image_version.rb +4 -0
  34. data/spec/factories/tim/provider_image.rb +4 -1
  35. data/spec/factories/tim/target_image.rb +6 -2
  36. data/spec/filters/resource_link_filter_spec.rb +158 -0
  37. data/spec/models/base_image_spec.rb +5 -5
  38. data/spec/models/dummy/pool_family_spec.rb +1 -1
  39. data/spec/models/dummy/provider_account_spec.rb +3 -1
  40. data/spec/models/dummy/provider_type_spec.rb +2 -1
  41. data/spec/models/dummy/user_spec.rb +1 -1
  42. data/spec/models/image_version_spec.rb +4 -3
  43. data/spec/models/provider_image_spec.rb +28 -5
  44. data/spec/models/target_image_spec.rb +74 -4
  45. data/spec/models/template_spec.rb +11 -2
  46. data/spec/validators/template_validator_spec.rb +10 -0
  47. data/spec/views/base_images_spec.rb +2 -1
  48. data/spec/views/provider_images_spec.rb +1 -0
  49. data/test/dummy/app/decorators/tim/controllers/base_images_controller_decorator.rb +13 -0
  50. data/test/dummy/config/database.yml +0 -3
  51. data/test/dummy/config/initializers/tim.rb +1 -1
  52. data/test/dummy/config/routes.rb +6 -1
  53. data/test/dummy/db/migrate/20121216133232_add_provider_account_id_to_provider_images.rb +5 -0
  54. data/test/dummy/db/schema.rb +28 -24
  55. data/tim.gemspec +32 -0
  56. metadata +46 -60
  57. data/test/dummy/db/development.sqlite3 +0 -0
  58. data/test/dummy/db/test.sqlite3 +0 -0
  59. data/test/dummy/log/development.log +0 -982
  60. data/test/dummy/log/test.log +0 -8629
  61. data/test/dummy/tmp/cache/assets/C7A/BB0/sprockets%2F13445f7a19078dd2df39517062aa6711 +0 -0
  62. data/test/dummy/tmp/cache/assets/C8C/CC0/sprockets%2F95d79f3b3096348427f3e4e38b5202e3 +0 -0
  63. data/test/dummy/tmp/cache/assets/CB0/2B0/sprockets%2F79106b90879c02a115d7f6f1c8390ac4 +0 -0
  64. data/test/dummy/tmp/cache/assets/CE0/690/sprockets%2F04c628c2a636286bfa92a4966b82b92a +0 -0
  65. data/test/dummy/tmp/cache/assets/D03/040/sprockets%2Fd9e94204d4b307145f12efc109b16c1f +0 -0
  66. data/test/dummy/tmp/cache/assets/D06/DC0/sprockets%2Fcd282851b6e4c463409ba3ece67e0510 +0 -0
  67. data/test/dummy/tmp/cache/assets/D23/5F0/sprockets%2F2a521f3183c6bbcd71bd26a5490b201e +0 -0
  68. data/test/dummy/tmp/cache/assets/D64/3A0/sprockets%2F56ac1aed10c39b12a88cb1b30f668f58 +0 -0
  69. data/test/dummy/tmp/cache/assets/D69/BD0/sprockets%2F0aaf75cf34556b33a9fec534fe4d0415 +0 -0
  70. data/test/dummy/tmp/cache/assets/D87/D80/sprockets%2Fefa3c8b210e87358c7add88cd6f49597 +0 -0
  71. data/test/dummy/tmp/cache/assets/D89/200/sprockets%2Ff9b4e953c874ed6a87de6490d055d9db +0 -0
  72. data/test/dummy/tmp/cache/assets/DF4/430/sprockets%2F403bb1de60cae4325cebd7e6c389b8ad +0 -0
  73. data/test/dummy/tmp/cache/assets/E00/500/sprockets%2Faaddc5b6f2cb6b98930cc54cf4c64a95 +0 -0
  74. data/test/dummy/tmp/cache/assets/EB3/CE0/sprockets%2Fd75a89c9fffacd99bce7eed96844eafc +0 -0
@@ -2,26 +2,28 @@ module Tim
2
2
  class TargetImagesController < Tim::ApplicationController
3
3
  respond_to :json, :only => :update
4
4
 
5
+ prepend_before_filter ResourceLinkFilter.new({ :target_image => :image_version }),
6
+ :only => [:create]
5
7
  before_filter :factory_keys, :only => :update
6
8
 
7
9
  def index
8
10
  @target_images = Tim::TargetImage.all unless defined? @target_images
9
- respond_with @target_images
11
+ respond_with(@target_images, @respond_options)
10
12
  end
11
13
 
12
14
  def show
13
15
  @target_image = Tim::TargetImage.find(params[:id]) unless defined? @target_image
14
- respond_with @target_image
16
+ respond_with(@target_image, @respond_options)
15
17
  end
16
18
 
17
19
  def new
18
20
  @target_image = Tim::TargetImage.new unless defined? @target_image
19
- respond_with @target_image
21
+ respond_with(@target_image, @respond_options)
20
22
  end
21
23
 
22
24
  def edit
23
25
  @target_image = Tim::TargetImage.find(params[:id]) unless defined? @target_image
24
- respond_with @target_image
26
+ respond_with(@target_image, @respond_options)
25
27
  end
26
28
 
27
29
  def create
@@ -29,7 +31,7 @@ module Tim
29
31
  if @target_image.save
30
32
  flash[:notice] = 'Image version was successfully created.'
31
33
  end
32
- respond_with @target_image
34
+ respond_with(@target_image, @respond_options)
33
35
  end
34
36
 
35
37
  def update
@@ -37,7 +39,7 @@ module Tim
37
39
  if @target_image.update_attributes(params[:target_image])
38
40
  flash[:notice] = 'Target image was successfully updated.'
39
41
  end
40
- respond_with @target_image
42
+ respond_with(@target_image, @respond_options)
41
43
  end
42
44
 
43
45
  # DELETE /target_images/1
@@ -45,7 +47,7 @@ module Tim
45
47
  def destroy
46
48
  @target_image = Tim::TargetImage.find(params[:id]) unless defined? @target_image
47
49
  @target_image.destroy
48
- respond_with @target_image
50
+ respond_with(@target_image, @respond_options)
49
51
  end
50
52
 
51
53
  # TODO Add factory permission check
@@ -3,22 +3,22 @@ module Tim
3
3
 
4
4
  def index
5
5
  @templates = Tim::Template.all unless defined? @templates
6
- respond_with @templates
6
+ respond_with(@templates, @respond_options)
7
7
  end
8
8
 
9
9
  def show
10
10
  @template = Tim::Template.find(params[:id]) unless defined? @template
11
- respond_with @template
11
+ respond_with(@template, @respond_options)
12
12
  end
13
13
 
14
14
  def new
15
15
  @template = Tim::Template.new unless defined? @template
16
- respond_with @template
16
+ respond_with(@template, @respond_options)
17
17
  end
18
18
 
19
19
  def edit
20
20
  @template = Tim::Template.find(params[:id])
21
- respond_with @template
21
+ respond_with(@template, @respond_options)
22
22
  end
23
23
 
24
24
  def create
@@ -26,7 +26,7 @@ module Tim
26
26
  if @template.save
27
27
  flash[:notice] = 'Template was successfully created.'
28
28
  end
29
- respond_with @template
29
+ respond_with(@template, @respond_options)
30
30
  end
31
31
 
32
32
  def update
@@ -34,13 +34,13 @@ module Tim
34
34
  if @template.update_attributes(params[:template])
35
35
  flash[:notice] = 'Template was successfully updated.'
36
36
  end
37
- respond_with @template
37
+ respond_with(@template, @respond_options)
38
38
  end
39
39
 
40
40
  def destroy
41
41
  @template = Tim::Template.find(params[:id]) unless defined? @template
42
42
  @template.destroy
43
- respond_with @template
43
+ respond_with(@template, @respond_options)
44
44
  end
45
45
 
46
46
  end
@@ -0,0 +1,6 @@
1
+ module Tim
2
+ module Error
3
+ class ImagefactoryConnectionRefused < ::StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,56 @@
1
+ # Before filter for controllers that handle REST API requests. Conductor uses
2
+ # linking to resources like <pool id='1'/> (resulting in :pool => { :id => 1 }
3
+ # in request params hash) but ActiveRecord expects linking to resources like
4
+ # <pool_id>1</pool_id> (resluting in :pool_id => 1 in request params hash).
5
+ #
6
+ # This before filter operates on request params hash to rewrite it from our
7
+ # format to ActiveRecord-friendly format.
8
+ #
9
+ # The constructor of the filter accepts specification of what should be
10
+ # transformed. E.g.:
11
+ #
12
+ # before_filter ResourceLinkFilter.new({ :catalog => :pool }),
13
+ # :only => [:create, :update]
14
+ #
15
+ # would transform { :catalog => { :pool => { :id => 1 }}}
16
+ # into { :catalog => { :pool_id => 1 }}
17
+ #
18
+ # See the specs in spec/util/resource_link_filter_spec.rb for more examples.
19
+ #
20
+
21
+ # NOTE This filter was taken from the aeolus conductor project: https://github.com/aeolusproject/conductor/
22
+
23
+ class Tim::ResourceLinkFilter
24
+ def initialize(resource_links)
25
+ @resource_links = resource_links
26
+ end
27
+
28
+ def before(controller)
29
+ return unless controller.request.format == :xml
30
+
31
+ transform_resource_links_recursively(controller.params, @resource_links)
32
+ end
33
+
34
+
35
+ private
36
+
37
+ def transform_resource_links_recursively(subparams, sublinks)
38
+ return if subparams == nil
39
+
40
+ case sublinks
41
+ when Symbol # then transform the link (last level of recursion)
42
+ return if subparams[sublinks] == nil || subparams[sublinks][:id] == nil
43
+
44
+ subparams[:"#{sublinks}_id"] = subparams[sublinks][:id]
45
+ subparams.delete(sublinks)
46
+ when Array # then process each item
47
+ sublinks.each do |item|
48
+ transform_resource_links_recursively(subparams, item)
49
+ end
50
+ when Hash # then descend into each entry
51
+ sublinks.each_key do |key|
52
+ transform_resource_links_recursively(subparams[key], sublinks[key])
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,13 +1,17 @@
1
1
  module Tim
2
2
  class BaseImage < Tim::Base
3
- has_many :image_versions
4
- belongs_to :template
3
+ has_many :image_versions, :inverse_of => :base_image
4
+ belongs_to :template, :inverse_of => :base_images
5
+
5
6
  belongs_to :user, :class_name => Tim.user_class
6
7
 
7
8
  accepts_nested_attributes_for :template
8
9
  accepts_nested_attributes_for :image_versions
9
10
 
10
- attr_accessible :template, :name, :description
11
+ validates_presence_of :name
12
+ validates_presence_of :template, :unless => :import
13
+
14
+ attr_accessible :template, :name, :description, :import
11
15
  attr_accessible :template_attributes
12
16
  attr_accessible :image_versions_attributes, :as => :admin
13
17
  attr_protected :id
@@ -1,11 +1,13 @@
1
1
  module Tim
2
2
  class ImageVersion < Tim::Base
3
- belongs_to :base_image
4
- has_many :target_images
3
+ belongs_to :base_image, :inverse_of => :image_versions
4
+ has_many :target_images, :inverse_of => :image_version
5
5
 
6
6
  accepts_nested_attributes_for :base_image
7
7
  accepts_nested_attributes_for :target_images
8
8
 
9
+ validates_presence_of :base_image
10
+
9
11
  attr_accessible :base_image_attributes
10
12
  attr_accessible :target_images_versions_attributes
11
13
  attr_protected :id
@@ -1,37 +1,63 @@
1
1
  module Tim
2
2
  class ProviderImage < Tim::Base
3
- belongs_to :target_image
3
+ belongs_to :target_image, :inverse_of => :provider_images
4
4
  belongs_to :provider_account, :class_name => Tim.provider_account_class
5
5
 
6
6
  accepts_nested_attributes_for :target_image
7
7
 
8
8
  attr_accessible :target_image_attributes
9
+ attr_accessible :external_image_id, :if => :imported?
9
10
  attr_accessible :status, :status_detail, :progress #, :as => :image_factory
10
11
  attr_accessible :provider
11
12
  attr_writer :credentials
12
13
  attr_protected :id
13
14
 
14
- after_create :create_factory_provider_image
15
+ validates_presence_of :target_image
16
+ validates_presence_of :external_image_id, :if => :imported?
17
+
18
+ after_create :create_factory_provider_image, :unless => :imported?
19
+ after_create :create_import, :if => :imported?
20
+
21
+ def imported?
22
+ target_image.imported?
23
+ end
15
24
 
16
25
  private
26
+ def factory_provider_credentials
27
+ @credentials
28
+ end
29
+
30
+ def factory_provider
31
+ self.provider
32
+ end
33
+
17
34
  def create_factory_provider_image
18
35
  begin
19
- provider_image = ImageFactory::ProviderImage.new(:target_image_id => self.target_image.factory_id,
20
- :provider => self.provider,
21
- :credentials => @credentials,
36
+ provider_image = ImageFactory::ProviderImage.new(:credentials => factory_provider_credentials,
22
37
  # TODO Remove this when upgrading to 3.2
23
38
  # target conflicts with rails 3.0.10
24
39
  # ActiveRecord::Associations::Association#target
25
40
  :target => target_image.target,
26
41
  :parameters => "")
42
+ provider_image.provider = factory_provider
27
43
  # TODO There is a bug in ARes 3.0.10 that will add map name twice when setting in mass assign. So we set
28
44
  # parameters separately.
29
45
  # Setting parameters at mass assign results in json => {"target_image":"parameters":{"parameters":{"..."}}}"
30
46
  # This should be tested and removed if fixed in 3.2
31
47
  provider_image.parameters = { :callbacks => ["#{ImageFactory::ProviderImage.callback_url}/#{self.id}"] }
48
+ if target_image.snapshot?
49
+ provider_image.parameters[:snapshot] = true
50
+ provider_image.template = self.target_image.template.xml
51
+ else
52
+ provider_image.target_image_id = self.target_image.factory_id
53
+ end
54
+
32
55
  provider_image.save!
33
56
  populate_factory_fields(provider_image)
34
57
  self.save
58
+ rescue Errno::ECONNREFUSED
59
+ raise Tim::Error::ImagefactoryConnectionRefused.new("Unable to connect"\
60
+ " to Imagefactory server @ #{Tim::ImageFactory::Base.site}")
35
61
  rescue => e
36
62
  # TODO Add proper error handling
37
63
  raise e
@@ -45,7 +71,17 @@ module Tim
45
71
  self.progress = factory_provider_image.percent_complete
46
72
  self.provider = factory_provider_image.provider
47
73
  self.external_image_id = factory_provider_image.identifier_on_provider
48
- self.provider_account_id = factory_provider_image.provider_account_identifier
74
+ self.factory_provider_account_id = factory_provider_image.provider_account_identifier
75
+ end
76
+
77
+ # TODO At the moment this method simply sets fields to import defaults.
78
+ # We should investigate whether we can check the image exists and if the
79
+ # user can access it. Deltacloud?
80
+ def create_import
81
+ self.status = "COMPLETE"
82
+ self.progress = "COMPLETE"
83
+ self.status_detail = "Imported Image"
84
+ self.save
49
85
  end
50
86
  end
51
87
  end
@@ -1,12 +1,15 @@
1
1
  module Tim
2
2
  class TargetImage < Tim::Base
3
- belongs_to :image_version
3
+ belongs_to :image_version, :inverse_of => :target_images
4
+ has_many :provider_images, :inverse_of => :target_image
5
+
4
6
  belongs_to :provider_type, :class_name => Tim.provider_type_class
5
- has_many :provider_images
6
7
 
7
8
  accepts_nested_attributes_for :image_version
8
9
  accepts_nested_attributes_for :provider_images
9
10
 
11
+ validates_presence_of :image_version, :target
12
+
10
13
  attr_accessible :image_version_attributes
11
14
  attr_accessible :provider_images_attributes
12
15
  attr_accessible :status, :status_detail, :progress #, :as => :image_factory
@@ -14,25 +17,42 @@ module Tim
14
17
 
15
18
  attr_protected :id
16
19
 
17
- after_create :create_factory_target_image
20
+ after_create :create_factory_target_image, :if => :create_factory_target_image?
21
+ after_create :set_import_snapshot_status, :if => lambda { |t| t.imported? || t.snapshot? }
18
22
 
19
23
  def template
20
24
  image_version.base_image.template
21
25
  end
22
26
 
27
+ def imported?
28
+ image_version.base_image.import
29
+ end
30
+
31
+ def snapshot?
32
+ build_method == "SNAPSHOT"
33
+ end
34
+
23
35
  private
24
36
  def create_factory_target_image
25
37
  begin
26
- target_image = ImageFactory::TargetImage.new(:template => template.xml,
27
- :target => target,
28
- :parameters => nil)
29
- # A bug in ARes adds parameters twice to the resulting json
30
- # when a map is provided in mass assign
38
+ target_image = ImageFactory::TargetImage.new(:target => target,
39
+ :parameters => nil)
40
+ # A bug in ARes adds parameters twice to the resulting json when a map
41
+ # is provided in mass assign
31
42
  target_image.parameters = { :callbacks => ["#{ImageFactory::TargetImage.callback_url}/#{self.id}"] }
43
+
44
+ if image_version.factory_base_image_id
45
+ target_image.base_image_id = image_version.factory_base_image_id
46
+ else
47
+ target_image.template = template.xml
48
+ end
32
49
  target_image.save!
33
50
 
34
51
  populate_factory_fields(target_image)
35
52
  self.save
53
+ rescue Errno::ECONNREFUSED
54
+ raise Tim::Error::ImagefactoryConnectionRefused.new("Unable to connect"\
55
+ " to Imagefactory server @ #{Tim::ImageFactory::Base.site}")
36
56
  rescue => e
37
57
  # TODO Add proper error handling
38
58
  raise e
@@ -44,7 +64,26 @@ module Tim
44
64
  self.factory_id = factory_target_image.id
45
65
  self.status_detail = factory_target_image.status_detail.activity
46
66
  self.progress = factory_target_image.percent_complete
67
+ unless self.image_version.factory_base_image_id
68
+ image_version.factory_base_image_id = factory_target_image.base_image_id
69
+ image_version.save!
70
+ end
71
+ end
72
+
73
+ def set_import_snapshot_status
74
+ self.progress = "COMPLETE"
75
+ self.status = "COMPLETE"
76
+ if self.imported?
77
+ self.status_detail = "Imported Image"
78
+ elsif self.snapshot?
79
+ self.status_detail = "Snapshot Image"
80
+ end
81
+ self.save
47
82
  end
48
83
 
84
+ private
85
+ def create_factory_target_image?
86
+ !imported? && !snapshot?
87
+ end
49
88
  end
50
- end
89
+ end
@@ -5,15 +5,30 @@ module Tim
5
5
  include ActiveModel::Validations
6
6
  validates_with TemplateValidator
7
7
 
8
- has_many :base_images
8
+ has_many :base_images, :inverse_of => :template
9
9
 
10
10
  attr_accessible :xml
11
11
  attr_protected :id
12
12
 
13
+ OS = Struct.new(:name, :version, :arch)
14
+
13
15
  # Used in views to display the xml elements of this template
14
16
  def xml_elements
15
- ::Nokogiri::XML::Document.parse(xml).xpath("//template/*").to_xml
17
+ parsed_xml.xpath("//template/*").to_xml
18
+ end
19
+
20
+ def os
21
+ OS.new(
22
+ parsed_xml.xpath("//template/os/name").text,
23
+ parsed_xml.xpath("//template/os/version").text,
24
+ parsed_xml.xpath("//template/os/arch").text
25
+ )
16
26
  end
17
27
 
28
+ private
29
+
30
+ def parsed_xml
31
+ @parsed_xml ||= ::Nokogiri::XML::Document.parse(xml)
32
+ end
18
33
  end
19
- end
34
+ end
@@ -0,0 +1,29 @@
1
+ # There is a bug in Rails which causes issues when using accepts_nested_attrs
2
+ # and inverse_of on associations: https://github.com/rails/rails/issues/7809/
3
+ # This code was lifted from the pull request of the bug shown above. It solves
4
+ # the issue. Until this is merged into rails we will have to carry the patch
5
+ # ourselves.
6
+
7
+ module ActiveRecord
8
+ module AutosaveAssociation
9
+ extend ActiveSupport::Concern
10
+
11
+ # Returns whether or not this record has been changed in any way (including whether
12
+ # any of its nested autosave associations are likewise changed)
13
+ def changed_for_autosave?
14
+ @_changed_for_autosave_called ||= false
15
+ if @_changed_for_autosave_called
16
+ # traversing a cyclic graph of objects; stop it
17
+ result = false
18
+ else
19
+ begin
20
+ @_changed_for_autosave_called = true
21
+ result = new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
22
+ ensure
23
+ @_changed_for_autosave_called = false
24
+ end
25
+ end
26
+ result
27
+ end
28
+ end
29
+ end