filemapper_spec.rb 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. require 'spec_helper'
  2. require 'puppetx/filemapper'
  3. describe PuppetX::FileMapper do
  4. before do
  5. @ramtype = Puppet::Util::FileType.filetype(:ram)
  6. @flattype = stub 'Class<FileType<Flat>>'
  7. Puppet::Util::FileType.stubs(:filetype).with(:flat).returns @flattype
  8. end
  9. after :each do
  10. dummytype.defaultprovider = nil
  11. end
  12. let(:dummytype) do
  13. Puppet::Type.newtype(:dummy) do
  14. ensurable
  15. newparam(:name, :namevar => true)
  16. newparam(:dummy_param)
  17. newproperty(:dummy_property)
  18. end
  19. end
  20. let(:single_file_provider) do
  21. dummytype.provide(:single) do
  22. include PuppetX::FileMapper
  23. def self.target_files; ['/single/file/provider']; end
  24. def self.parse_file(filename, content)
  25. [{:name => 'yay', :dummy_param => :bla, :dummy_property => 'baz'}]
  26. end
  27. def select_file; '/single/file/provider'; end
  28. def self.format_file(filename, providers); 'flushback'; end
  29. end
  30. end
  31. let(:multiple_file_provider) do
  32. dummytype.provide(:multiple, :resource_type => dummytype) do
  33. include PuppetX::FileMapper
  34. def self.target_files; ['/multiple/file/provider-one', '/multiple/file/provider-two']; end
  35. def self.parse_file(filename, content)
  36. case filename
  37. when '/multiple/file/provider-one' then [{:name => 'yay', :dummy_param => :bla, :dummy_property => 'baz'}]
  38. when '/multiple/file/provider-two' then [{:name => 'whee', :dummy_param => :ohai, :dummy_property => 'wat'}]
  39. end
  40. end
  41. def select_file; '/multiple/file/provider-flush'; end
  42. def self.format_file(filename, providers); 'multiple flush content'; end
  43. end
  44. end
  45. let(:params_yay) { {:name => 'yay', :dummy_param => :bla, :dummy_property => 'baz'} }
  46. let(:params_whee) { {:name => 'whee', :dummy_param => :ohai, :dummy_property => 'wat'} }
  47. let(:params_nope) { {:name => 'dead', :dummy_param => :nofoo, :dummy_property => 'sadprop'} }
  48. after :each do
  49. dummytype.provider_hash.clear
  50. end
  51. describe 'when included' do
  52. it 'should initialize the provider as not failed' do
  53. provider = dummytype.provide(:foo) { include PuppetX::FileMapper }
  54. provider.should_not be_failed
  55. end
  56. describe 'when generating attr_accessors' do
  57. subject { multiple_file_provider.new(params_yay) }
  58. describe 'for properties' do
  59. it { should respond_to :dummy_property }
  60. it { should respond_to :dummy_property= }
  61. it { should respond_to :ensure }
  62. it { should respond_to :ensure= }
  63. end
  64. describe 'for parameters' do
  65. it { should_not respond_to :dummy_param }
  66. it { should_not respond_to :dummy_param= }
  67. end
  68. end
  69. end
  70. describe 'when validating the class' do
  71. describe "and it doesn't implement self.target_files" do
  72. subject do
  73. dummytype.provide(:incomplete) { include PuppetX::FileMapper }
  74. end
  75. it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /self.target_files/ }
  76. end
  77. describe "and it doesn't implement self.parse_file" do
  78. subject do
  79. dummytype.provide(:incomplete) do
  80. include PuppetX::FileMapper
  81. def self.target_files; end
  82. end
  83. end
  84. it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /self.parse_file/}
  85. end
  86. describe "and it doesn't implement #select_file" do
  87. subject do
  88. dummytype.provide(:incomplete) do
  89. include PuppetX::FileMapper
  90. def self.target_files; end
  91. def self.parse_file(filename, content); end
  92. def self.format_file(filename, resources); 'foo'; end
  93. end
  94. end
  95. it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /#select_file/}
  96. end
  97. describe "and it doesn't implement self.format_file" do
  98. subject do
  99. dummytype.provide(:incomplete) do
  100. include PuppetX::FileMapper
  101. def self.target_files; end
  102. def self.parse_file(filename, content); end
  103. def select_file; '/single/file/provider'; end
  104. end
  105. end
  106. it { expect { subject.validate_class! }.to raise_error Puppet::DevError, /self\.format_file/}
  107. end
  108. end
  109. describe 'when reading' do
  110. describe 'a single file' do
  111. subject { single_file_provider }
  112. it 'should generate a filetype for that file' do
  113. @flattype.expects(:new).with('/single/file/provider').once.returns @ramtype.new('/single/file/provider')
  114. subject.load_all_providers_from_disk
  115. end
  116. it 'should parse each file' do
  117. stub_file = stub(:read => 'file contents')
  118. @flattype.stubs(:new).with('/single/file/provider').once.returns stub_file
  119. subject.expects(:parse_file).with('/single/file/provider', 'file contents').returns []
  120. subject.load_all_providers_from_disk
  121. end
  122. it 'should return the generated array' do
  123. @flattype.stubs(:new).with('/single/file/provider').once.returns @ramtype.new('/single/file/provider')
  124. subject.load_all_providers_from_disk.should == [params_yay]
  125. end
  126. end
  127. describe 'multiple files' do
  128. subject { multiple_file_provider }
  129. it 'should generate a filetype for each file' do
  130. @flattype.expects(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
  131. @flattype.expects(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
  132. subject.load_all_providers_from_disk
  133. end
  134. describe 'when parsing' do
  135. before do
  136. @flattype.stubs(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
  137. @flattype.stubs(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
  138. end
  139. it 'should parse each file' do
  140. subject.expects(:parse_file).with('/multiple/file/provider-one', 'barbar').returns []
  141. subject.expects(:parse_file).with('/multiple/file/provider-two', 'bazbaz').returns []
  142. subject.load_all_providers_from_disk
  143. end
  144. it 'should return the generated array' do
  145. data = subject.load_all_providers_from_disk
  146. data.should be_include(params_yay)
  147. data.should be_include(params_whee)
  148. end
  149. end
  150. end
  151. describe 'validating input' do
  152. subject { multiple_file_provider }
  153. before do
  154. @flattype.stubs(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
  155. @flattype.stubs(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
  156. end
  157. it 'should ensure that retrieved values are in the right format' do
  158. subject.stubs(:parse_file).with('/multiple/file/provider-one', 'barbar').returns Hash.new
  159. subject.stubs(:parse_file).with('/multiple/file/provider-two', 'bazbaz').returns Hash.new
  160. expect { subject.load_all_providers_from_disk }.to raise_error Puppet::DevError, /expected.*to return an Array, got a Hash/
  161. end
  162. end
  163. end
  164. describe 'when generating instances' do
  165. subject { multiple_file_provider }
  166. before do
  167. @flattype.stubs(:new).with('/multiple/file/provider-one').once.returns(stub(:read => 'barbar'))
  168. @flattype.stubs(:new).with('/multiple/file/provider-two').once.returns(stub(:read => 'bazbaz'))
  169. end
  170. it 'should generate a provider instance from hashes' do
  171. params_yay.merge!({:provider => subject.name})
  172. params_whee.merge!({:provider => subject.name})
  173. subject.expects(:new).with(params_yay.merge({:ensure => :present})).returns stub()
  174. subject.expects(:new).with(params_whee.merge({:ensure => :present})).returns stub()
  175. subject.instances
  176. end
  177. it 'should generate a provider instance for each hash' do
  178. provs = subject.instances
  179. provs.should have(2).items
  180. provs.each { |prov| prov.should be_a_kind_of(Puppet::Provider)}
  181. end
  182. [
  183. {:name => 'yay', :dummy_property => 'baz'},
  184. {:name => 'whee', :dummy_property => 'wat'},
  185. ].each do |values|
  186. it "should match hash values to provider properties for #{values[:name]}" do
  187. provs = subject.instances
  188. prov = provs.find {|prov| prov.name == values[:name]}
  189. values.each_pair { |property, value| prov.send(property).should == value }
  190. end
  191. end
  192. end
  193. describe 'when prefetching' do
  194. subject { multiple_file_provider }
  195. let(:provider_yay) { subject.new(params_yay.merge({:provider => subject.name})) }
  196. let(:provider_whee) { subject.new(params_whee.merge({:provider => subject.name})) }
  197. before do
  198. subject.stubs(:instances).returns [provider_yay, provider_whee]
  199. end
  200. let(:resources) do
  201. [params_yay, params_whee, params_nope].inject({}) do |h, params|
  202. h[params[:name]] = dummytype.new(params)
  203. h
  204. end
  205. end
  206. it "should update resources with existing providers" do
  207. resources['yay'].expects(:provider=).with(provider_yay)
  208. resources['whee'].expects(:provider=).with(provider_whee)
  209. subject.prefetch(resources)
  210. end
  211. it "should not update resources that don't have providers" do
  212. resources['dead'].expects(:provider=).never
  213. subject.prefetch(resources)
  214. end
  215. end
  216. describe 'on resource state change' do
  217. subject { multiple_file_provider }
  218. before do
  219. dummytype.defaultprovider = subject
  220. subject.any_instance.stubs(:resource_type).returns dummytype
  221. end
  222. describe 'from absent to present' do
  223. let(:resource) { dummytype.new(:name => 'boom', :dummy_property => 'bang') }
  224. it 'should mark the related file as dirty' do
  225. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
  226. resource.property(:ensure).sync
  227. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_true
  228. end
  229. end
  230. describe 'from present to absent' do
  231. it 'should mark the related file as dirty' do
  232. resource = dummytype.new(:name => 'boom', :dummy_property => 'bang', :ensure => :absent)
  233. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
  234. resource.property(:ensure).sync
  235. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_true
  236. end
  237. end
  238. describe 'on a property' do
  239. let(:resource) { resource = dummytype.new(params_yay) }
  240. before do
  241. prov = subject.new(params_yay.merge({:ensure => :present}))
  242. subject.stubs(:instances).returns [prov]
  243. subject.prefetch({params_yay[:name] => resource})
  244. end
  245. it 'should mark the related file as dirty' do
  246. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
  247. resource.property(:dummy_property).value = 'new value'
  248. resource.property(:dummy_property).sync
  249. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_true
  250. end
  251. end
  252. describe 'on a parameter' do
  253. let(:resource) { resource = dummytype.new(params_yay) }
  254. before do
  255. prov = subject.new(params_yay.merge({:ensure => :present}))
  256. subject.stubs(:instances).returns [prov]
  257. subject.prefetch({params_yay[:name] => resource})
  258. end
  259. it 'should not mark the related file as dirty' do
  260. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
  261. resource.parameter(:dummy_param).value = 'new value'
  262. resource.flush
  263. subject.mapped_files['/multiple/file/provider-flush'][:dirty].should be_false
  264. end
  265. end
  266. end
  267. describe 'when determining whether to flush' do
  268. subject { multiple_file_provider }
  269. before do
  270. dummytype.defaultprovider = subject
  271. subject.any_instance.stubs(:resource_type).returns dummytype
  272. end
  273. let(:resource) { resource = dummytype.new(params_yay) }
  274. it 'should refuse to flush if the provider is in a failed state' do
  275. subject.dirty_file!('/multiple/file/provider-flush')
  276. subject.failed!
  277. subject.expects(:collect_resources_for_provider).never
  278. resource.flush
  279. end
  280. it 'should use the provider instance method `select_file` to locate the destination file' do
  281. resource.provider.expects(:select_file).returns '/multiple/file/provider-flush'
  282. resource.property(:dummy_property).value = 'zoom'
  283. resource.property(:dummy_property).sync
  284. end
  285. it 'should trigger the class dirty_file! method' do
  286. subject.expects(:dirty_file!).with('/multiple/file/provider-flush')
  287. resource.property(:dummy_property).value = 'zoom'
  288. resource.property(:dummy_property).sync
  289. end
  290. end
  291. describe 'when flushing' do
  292. subject { multiple_file_provider }
  293. let(:newtype) { @ramtype.new('/multiple/file/provider-flush') }
  294. let(:resource) { resource = dummytype.new(params_yay) }
  295. before { newtype.stubs(:backup) }
  296. it 'should forward provider#flush to the class' do
  297. subject.expects(:flush_file).with('/multiple/file/provider-flush')
  298. resource.flush
  299. end
  300. it 'should generate filetypes for new files' do
  301. subject.dirty_file!('/multiple/file/provider-flush')
  302. @flattype.expects(:new).with('/multiple/file/provider-flush').returns newtype
  303. resource.flush
  304. end
  305. it 'should use existing filetypes for existing files' do
  306. stub_filetype = stub()
  307. stub_filetype.expects(:backup)
  308. stub_filetype.expects(:write)
  309. subject.dirty_file!('/multiple/file/provider-flush')
  310. subject.mapped_files['/multiple/file/provider-flush'][:filetype] = stub_filetype
  311. resource.flush
  312. end
  313. it 'should trigger a flush on dirty files' do
  314. subject.dirty_file!('/multiple/file/provider-flush')
  315. subject.expects(:perform_write).with('/multiple/file/provider-flush', 'multiple flush content')
  316. resource.flush
  317. end
  318. it 'should not flush clean files' do
  319. subject.expects(:perform_write).never
  320. resource.flush
  321. end
  322. end
  323. describe 'validating the file contents to flush' do
  324. subject { multiple_file_provider }
  325. before do
  326. subject.stubs(:format_file).returns ['definitely', 'not', 'of', 'class', 'String']
  327. subject.dirty_file!('/multiple/file/provider-flush')
  328. end
  329. it 'should raise an error if given an invalid value for file contents' do
  330. subject.expects(:perform_write).with('/multiple/file/provider-flush', %w{invalid data}).never
  331. expect { subject.flush_file('/multiple/file/provider-flush') }.to raise_error Puppet::DevError, /expected .* to return a String, got a Array/
  332. end
  333. end
  334. describe 'when unlinking empty files' do
  335. subject { multiple_file_provider }
  336. let(:newtype) { @ramtype.new('/multiple/file/provider-flush') }
  337. before do
  338. subject.unlink_empty_files = true
  339. newtype.stubs(:backup)
  340. File.stubs(:unlink)
  341. end
  342. describe 'with empty file contents' do
  343. before do
  344. subject.dirty_file!('/multiple/file/provider-flush')
  345. @flattype.stubs(:new).with('/multiple/file/provider-flush').returns newtype
  346. File.stubs(:exist?).with('/multiple/file/provider-flush').returns true
  347. subject.stubs(:format_file).returns ''
  348. end
  349. it 'should back up the file' do
  350. newtype.expects(:backup)
  351. subject.flush_file('/multiple/file/provider-flush')
  352. end
  353. it 'should remove the file' do
  354. File.expects(:unlink).with('/multiple/file/provider-flush')
  355. subject.flush_file('/multiple/file/provider-flush')
  356. end
  357. it 'should not write to the file' do
  358. subject.expects(:perform_write).with('/multiple/file/provider-flush', '').never
  359. subject.flush_file('/multiple/file/provider-flush')
  360. end
  361. end
  362. describe 'with empty file contents and no destination file' do
  363. before do
  364. subject.dirty_file!('/multiple/file/provider-flush')
  365. @flattype.stubs(:new).with('/multiple/file/provider-flush').returns newtype
  366. File.stubs(:exist?).with('/multiple/file/provider-flush').returns false
  367. subject.stubs(:format_file).returns ''
  368. end
  369. it 'should not try to remove the file' do
  370. File.expects(:exist?).with('/multiple/file/provider-flush').returns false
  371. File.expects(:unlink).never
  372. subject.flush_file('/multiple/file/provider-flush')
  373. end
  374. it 'should not try to back up the file' do
  375. newtype.expects(:backup).never
  376. subject.flush_file('/multiple/file/provider-flush')
  377. end
  378. end
  379. describe 'with a non-empty file' do
  380. before do
  381. subject.dirty_file!('/multiple/file/provider-flush')
  382. @flattype.stubs(:new).with('/multiple/file/provider-flush').returns newtype
  383. File.stubs(:exist?).with('/multiple/file/provider-flush').returns true
  384. subject.stubs(:format_file).returns 'not empty'
  385. end
  386. it 'should not remove the file' do
  387. File.expects(:unlink).never
  388. subject.flush_file('/multiple/file/provider-flush')
  389. end
  390. end
  391. end
  392. describe 'flush hooks' do
  393. subject { multiple_file_provider }
  394. before :each do
  395. subject.dirty_file!('/multiple/file/provider-flush')
  396. end
  397. let(:newtype) { @ramtype.new('/multiple/file/provider-flush') }
  398. it 'should be called in order' do
  399. seq = sequence('flush')
  400. subject.expects(:respond_to?).with(:pre_flush_hook).returns true
  401. subject.expects(:respond_to?).with(:post_flush_hook).returns true
  402. subject.expects(:pre_flush_hook).with('/multiple/file/provider-flush').in_sequence(seq)
  403. subject.expects(:perform_write).with('/multiple/file/provider-flush', 'multiple flush content').in_sequence(seq)
  404. subject.expects(:post_flush_hook).with('/multiple/file/provider-flush').in_sequence(seq)
  405. subject.flush_file '/multiple/file/provider-flush'
  406. end
  407. it 'should call post_flush_hook even if an exception is raised' do
  408. subject.stubs(:respond_to?).with(:pre_flush_hook).returns false
  409. subject.stubs(:respond_to?).with(:post_flush_hook).returns true
  410. subject.expects(:perform_write).with('/multiple/file/provider-flush', 'multiple flush content').raises RuntimeError
  411. subject.expects(:post_flush_hook)
  412. expect { subject.flush_file '/multiple/file/provider-flush' }.to raise_error RuntimeError
  413. end
  414. end
  415. describe 'when formatting resources for flushing' do
  416. let(:provider_class) { multiple_file_provider }
  417. let(:new_resource) { dummytype.new(params_yay) }
  418. let(:current_provider) { provider_class.new(params_whee) }
  419. let(:current_resource) { dummytype.new(params_whee) }
  420. let(:remove_provider) { provider_class.new(params_nope) }
  421. let(:remove_resource) { dummytype.new(params_nope.merge({:ensure => :absent})) }
  422. let(:unmanaged_provider) { provider_class.new(:name => 'unmanaged_resource', :dummy_param => 'zoom', :dummy_property => 'squid', :ensure => :present) }
  423. let(:provider_stubs) { [current_provider, remove_provider, unmanaged_provider] }
  424. let(:resource_stubs) { [new_resource, current_resource, remove_resource] }
  425. before do
  426. dummytype.defaultprovider = provider_class
  427. provider_class.any_instance.stubs(:resource_type).returns dummytype
  428. provider_class.stubs(:instances).returns provider_stubs
  429. provider_class.prefetch(resource_stubs.inject({}) { |h, r| h[r.name] = r; h})
  430. # Pretend that we're the resource harness and apply the ensure param
  431. resource_stubs.each { |r| r.property(:ensure).sync }
  432. end
  433. it 'should collect all resources for a given file' do
  434. provider_class.expects(:collect_providers_for_file).with('/multiple/file/provider-flush').returns []
  435. provider_class.stubs(:perform_write)
  436. provider_class.flush_file('/multiple/file/provider-flush')
  437. end
  438. describe 'and selecting' do
  439. subject { multiple_file_provider.collect_providers_for_file('/multiple/file/provider-flush').map(&:name) }
  440. describe 'present resources' do
  441. it { should be_include 'yay' }
  442. it { should be_include 'whee' }
  443. it { should be_include 'unmanaged_resource' }
  444. end
  445. describe 'absent resources' do
  446. it { should_not be_include 'nope' }
  447. end
  448. end
  449. end
  450. end