fission 0.4.0 → 0.5.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. data/.ruby-gemset +1 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +12 -3
  5. data/README.md +45 -19
  6. data/bin/fission +1 -1
  7. data/fission.gemspec +3 -2
  8. data/lib/fission.rb +15 -0
  9. data/lib/fission/action/shell_executor.rb +37 -0
  10. data/lib/fission/action/snapshot/creator.rb +81 -0
  11. data/lib/fission/action/snapshot/deleter.rb +85 -0
  12. data/lib/fission/action/snapshot/lister.rb +90 -0
  13. data/lib/fission/action/snapshot/reverter.rb +81 -0
  14. data/lib/fission/action/vm/cloner.rb +191 -0
  15. data/lib/fission/action/vm/deleter.rb +73 -0
  16. data/lib/fission/action/vm/lister.rb +138 -0
  17. data/lib/fission/action/vm/starter.rb +88 -0
  18. data/lib/fission/action/vm/stopper.rb +79 -0
  19. data/lib/fission/action/vm/suspender.rb +68 -0
  20. data/lib/fission/cli.rb +21 -117
  21. data/lib/fission/command.rb +39 -0
  22. data/lib/fission/command/clone.rb +11 -6
  23. data/lib/fission/command/delete.rb +11 -6
  24. data/lib/fission/command/info.rb +62 -0
  25. data/lib/fission/command/snapshot_create.rb +9 -3
  26. data/lib/fission/command/snapshot_delete.rb +38 -0
  27. data/lib/fission/command/snapshot_list.rb +9 -3
  28. data/lib/fission/command/snapshot_revert.rb +9 -3
  29. data/lib/fission/command/start.rb +11 -6
  30. data/lib/fission/command/status.rb +9 -1
  31. data/lib/fission/command/stop.rb +9 -3
  32. data/lib/fission/command/suspend.rb +11 -6
  33. data/lib/fission/command_helpers.rb +18 -4
  34. data/lib/fission/command_line_parser.rb +189 -0
  35. data/lib/fission/config.rb +1 -1
  36. data/lib/fission/fusion.rb +3 -4
  37. data/lib/fission/lease.rb +2 -1
  38. data/lib/fission/metadata.rb +6 -1
  39. data/lib/fission/response.rb +17 -9
  40. data/lib/fission/version.rb +1 -1
  41. data/lib/fission/vm.rb +142 -382
  42. data/lib/fission/vm_configuration.rb +79 -0
  43. data/spec/fission/action/execute_shell_command_spec.rb +25 -0
  44. data/spec/fission/action/snapshot/creator_spec.rb +77 -0
  45. data/spec/fission/action/snapshot/deleter_spec.rb +84 -0
  46. data/spec/fission/action/snapshot/lister_spec.rb +67 -0
  47. data/spec/fission/action/snapshot/reverter_spec.rb +76 -0
  48. data/spec/fission/action/vm/cloner_spec.rb +198 -0
  49. data/spec/fission/action/vm/deleter_spec.rb +79 -0
  50. data/spec/fission/action/vm/lister_spec.rb +164 -0
  51. data/spec/fission/action/vm/starter_spec.rb +88 -0
  52. data/spec/fission/action/vm/stopper_spec.rb +71 -0
  53. data/spec/fission/action/vm/suspender_spec.rb +59 -0
  54. data/spec/fission/cli_spec.rb +32 -157
  55. data/spec/fission/command/clone_spec.rb +9 -3
  56. data/spec/fission/command/delete_spec.rb +11 -3
  57. data/spec/fission/command/info_spec.rb +130 -0
  58. data/spec/fission/command/snapshot_create_spec.rb +11 -3
  59. data/spec/fission/command/snapshot_delete_spec.rb +74 -0
  60. data/spec/fission/command/snapshot_list_spec.rb +11 -3
  61. data/spec/fission/command/snapshot_revert_spec.rb +11 -3
  62. data/spec/fission/command/start_spec.rb +11 -3
  63. data/spec/fission/command/status_spec.rb +16 -5
  64. data/spec/fission/command/stop_spec.rb +11 -3
  65. data/spec/fission/command/suspend_spec.rb +11 -3
  66. data/spec/fission/command_helpers_spec.rb +27 -5
  67. data/spec/fission/command_line_parser_spec.rb +267 -0
  68. data/spec/fission/command_spec.rb +16 -1
  69. data/spec/fission/config_spec.rb +3 -3
  70. data/spec/fission/fusion_spec.rb +11 -6
  71. data/spec/fission/lease_spec.rb +81 -45
  72. data/spec/fission/metadata_spec.rb +29 -6
  73. data/spec/fission/response_spec.rb +20 -9
  74. data/spec/fission/ui_spec.rb +1 -1
  75. data/spec/fission/vm_configuration_spec.rb +132 -0
  76. data/spec/fission/vm_spec.rb +393 -750
  77. data/spec/helpers/command_helpers.rb +1 -1
  78. metadata +93 -15
  79. data/.rvmrc +0 -1
@@ -0,0 +1,81 @@
1
+ module Fission
2
+ module Action
3
+ module Snapshot
4
+
5
+ class Reverter
6
+
7
+ # Internal: Creates a new SnapshotReverter object. This accepts a VM
8
+ # object.
9
+ #
10
+ # vm - An instance of VM
11
+ #
12
+ # Examples:
13
+ #
14
+ # Fission::Action::SnapshotReverter.new @my_vm
15
+ #
16
+ # Returns a new SnapshotReverter object
17
+ def initialize(vm)
18
+ @vm = vm
19
+ end
20
+
21
+ # Public: Reverts the VM to the specified snapshot. The snapshot to
22
+ # revert to must exist and the Fusion GUI must not be running.
23
+ #
24
+ # name - The snapshot name to revert to.
25
+ #
26
+ # Examples
27
+ #
28
+ # @reverter.revert_to_snapshot('foo_snap_1')
29
+ #
30
+ # Returns a Response with the result.
31
+ # If successful, the Response's data attribute will be nil.
32
+ # If there is an error, an unsuccessful Response will be returned.
33
+ def revert_to_snapshot(name)
34
+ unless @vm.exists?
35
+ return Response.new :code => 1, :message => 'VM does not exist'
36
+ end
37
+
38
+ if Fusion.running?
39
+ message = 'It looks like the Fusion GUI is currently running. '
40
+ message << 'A VM cannot be reverted to a snapshot when the Fusion GUI is running. '
41
+ message << 'Exit the Fusion GUI and try again.'
42
+ return Response.new :code => 1, :message => message
43
+ end
44
+
45
+ conf_file_response = @vm.conf_file
46
+ return conf_file_response unless conf_file_response.successful?
47
+
48
+ snapshots_response = @vm.snapshots
49
+ return snapshots_response unless snapshots_response.successful?
50
+
51
+ unless snapshots_response.data.include? name
52
+ message = "Unable to find a snapshot named '#{name}'."
53
+ return Response.new :code => 1, :message => message
54
+ end
55
+
56
+ command = "#{vmrun_cmd} revertToSnapshot "
57
+ command << "'#{conf_file_response.data}' \"#{name}\" 2>&1"
58
+
59
+ command_exec = Fission::Action::ShellExecutor.new command
60
+ Response.from_shell_executor command_exec.execute
61
+ end
62
+
63
+ private
64
+ # Internal: Helper for getting the configured vmrun_cmd value.
65
+ #
66
+ # Examples
67
+ #
68
+ # @reverter.vmrun_cmd
69
+ # # => "/foo/bar/vmrun -T fusion"
70
+ #
71
+ # Returns a String for the configured value of
72
+ # Fission.config['vmrun_cmd'].
73
+ def vmrun_cmd
74
+ Fission.config['vmrun_cmd']
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,191 @@
1
+ module Fission
2
+ module Action
3
+ module VM
4
+
5
+ class Cloner
6
+
7
+ # Internal: Creates a new VMCloner object. This accepts a source and
8
+ # target VM object.
9
+ #
10
+ # source_vm - An instance of VM
11
+ # target_vm - An instance of VM
12
+ #
13
+ # Examples:
14
+ #
15
+ # Fission::Action::VMCloner.new @my_source_vm, @my_target_vm
16
+ #
17
+ # Returns a new VMCloner object.
18
+ def initialize(source_vm, target_vm)
19
+ @source_vm = source_vm
20
+ @target_vm = target_vm
21
+ end
22
+
23
+ # Public: Creates a new VM which is a clone of an existing VM. As
24
+ # Fusion doesn't provide a native cloning mechanism, this is a best
25
+ # effort. This essentially is a directory copy with updates to relevant
26
+ # files. It's recommended to clone VMs which are not running.
27
+ #
28
+ # Examples
29
+ #
30
+ # @cloner.clone
31
+ #
32
+ # Returns a Response with the result.
33
+ # If successful, the Response's data attribute will be nil.
34
+ # If there is an error, an unsuccessful Response will be returned.
35
+ def clone
36
+ unless @source_vm.exists?
37
+ return Response.new :code => 1, :message => 'VM does not exist'
38
+ end
39
+
40
+ if @target_vm.exists?
41
+ return Response.new :code => 1, :message => 'VM already exists'
42
+ end
43
+
44
+ FileUtils.cp_r @source_vm.path, @target_vm.path
45
+
46
+ rename_vm_files @source_vm.name, @target_vm.name
47
+ update_config @source_vm.name, @target_vm.name
48
+
49
+ Response.new :code => 0
50
+ end
51
+
52
+ private
53
+ # Internal: Renames the files of a newly cloned VM.
54
+ #
55
+ # from - The VM name that was used as the source of the clone.
56
+ # to - The name of the newly cloned VM.
57
+ #
58
+ # Examples
59
+ #
60
+ # @cloner.rename_vm_files 'foo', 'bar'
61
+ #
62
+ # Returns nothing.
63
+ def rename_vm_files(from, to)
64
+ files_to_rename(from, to).each do |file|
65
+ text_to_replace = File.basename(file, File.extname(file))
66
+
67
+ if File.extname(file) == '.vmdk'
68
+ if file.match /\-s\d+\.vmdk/
69
+ text_to_replace = file.partition(/\-s\d+.vmdk/).first
70
+ end
71
+ end
72
+
73
+ unless File.exists?(File.join(@target_vm.path, file.gsub(text_to_replace, to)))
74
+ FileUtils.mv File.join(@target_vm.path, file),
75
+ File.join(@target_vm.path, file.gsub(text_to_replace, to))
76
+ end
77
+ end
78
+ end
79
+
80
+ # Internal: Provides the list of files which need to be renamed in a
81
+ # newly cloned VM directory.
82
+ #
83
+ # from - The VM name that was used as the source of the clone.
84
+ # to - The name of the newly cloned VM.
85
+ #
86
+ # Examples
87
+ #
88
+ # @cloner.files_to_rename 'foo', 'bar'
89
+ # # => ['/vms/vm1/foo.vmdk', '/vms/vm1/foo.vmx', 'vms/vm1/blah.other']
90
+ #
91
+ # Returns an Array containing the paths (String) to the files to rename.
92
+ # The paths which match the from name will preceed any other files
93
+ # found in the newly cloned VM directory.
94
+ def files_to_rename(from, to)
95
+ files_which_match_source_vm = []
96
+ other_files = []
97
+
98
+ Dir.entries(@target_vm.path).each do |f|
99
+ unless f == '.' || f == '..'
100
+ f.include?(from) ? files_which_match_source_vm << f : other_files << f
101
+ end
102
+ end
103
+
104
+ files_which_match_source_vm + other_files
105
+ end
106
+
107
+ # Internal: Provides the list of file extensions for VM related files.
108
+ #
109
+ # Examples
110
+ #
111
+ # @cloner.vm_file_extension
112
+ # # => ['.nvram', '.vmdk', '.vmem']
113
+ #
114
+ # Returns an Array containing the file extensions of VM realted files.
115
+ # The file extensions returned are Strings and include a '.'.
116
+ def vm_file_extensions
117
+ ['.nvram', '.vmdk', '.vmem', '.vmsd', '.vmss', '.vmx', '.vmxf']
118
+ end
119
+
120
+ # Internal: Updates config files for a newly cloned VM. This will
121
+ # update any files with the extension of '.vmx', '.vmxf', and '.vmdk'.
122
+ # Any binary '.vmdk' files will be skipped.
123
+ #
124
+ # from - The VM name that was used as the source of the clone.
125
+ # to - The name of the newly cloned VM.
126
+ #
127
+ # Examples
128
+ #
129
+ # @cloner.update_config 'foo', 'bar'
130
+ #
131
+ # Returns nothing.
132
+ def update_config(from, to)
133
+ ['.vmx', '.vmxf', '.vmdk'].each do |ext|
134
+ file = File.join @target_vm.path, "#{to}#{ext}"
135
+
136
+ unless File.binary?(file)
137
+ text = (File.read file).gsub from, to
138
+ File.open(file, 'w'){ |f| f.print text }
139
+ end
140
+
141
+ clean_up_conf_file(file) if ext == '.vmx'
142
+ end
143
+ end
144
+
145
+ # Internal: Cleans up the conf file (*.vmx) for a newly cloned VM. This
146
+ # includes removing generated MAC addresses, setting up for a new UUID,
147
+ # and disable VMware tools warning.
148
+ #
149
+ # conf_file_path - Aboslute path to the VM's conf file (.vmx).
150
+ #
151
+ # Examples
152
+ #
153
+ # @cloner.clean_up_conf_file '/vms/foo/foo.vmx'
154
+ #
155
+ # Returns nothing.
156
+ def clean_up_conf_file(conf_file_path)
157
+ conf_items_patterns = { /^tools\.remindInstall.*\n/ => "tools.remindInstall = \"FALSE\"",
158
+ /^uuid\.action.*\n/ => "uuid.action = \"create\"",
159
+ /^ethernet\.+generatedAddress.*\n/ => '' }
160
+
161
+ content = File.read conf_file_path
162
+ content << "\n"
163
+
164
+ conf_items_patterns.each_pair do |pattern, new_item|
165
+ unless content.include? new_item
166
+ content.gsub(pattern, '').strip
167
+ content << "#{new_item}\n"
168
+ end
169
+ end
170
+
171
+ File.open(conf_file_path, 'w') { |f| f.print content }
172
+ end
173
+
174
+ # Internal: Helper for getting the configured vmrun_cmd value.
175
+ #
176
+ # Examples
177
+ #
178
+ # @cloner.vmrun_cmd
179
+ # # => "/foo/bar/vmrun -T fusion"
180
+ #
181
+ # Returns a String for the configured value of
182
+ # Fission.config['vmrun_cmd'].
183
+ def vmrun_cmd
184
+ Fission.config['vmrun_cmd']
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,73 @@
1
+ module Fission
2
+ module Action
3
+ module VM
4
+
5
+ class Deleter
6
+
7
+ # Internal: Creates a new VMDeleter object. This accepts a VM object.
8
+ #
9
+ # vm - An instance of VM
10
+ #
11
+ # Examples:
12
+ #
13
+ # Fission::Action::VMDeleter.new @my_vm
14
+ #
15
+ # Returns a new VMDeleter object
16
+ def initialize(vm)
17
+ @vm = vm
18
+ end
19
+
20
+ # Public: Deletes a VM. The VM must not be running in order to delete
21
+ # it. As there are a number issues with the Fusion command line tool for
22
+ # deleting VMs, this is a best effort. The VM must not be running when
23
+ # this method is called. This essentially deletes the VM directory and
24
+ # attempts to remove the relevant entries from the Fusion plist file.
25
+ # It's highly recommended to delete VMs without the Fusion GUI running.
26
+ # If the Fusion GUI is running this method should succeed, but it's
27
+ # been observed that Fusion will recreate the plist data which is
28
+ # deleted. This leads to 'missing' VMs in the Fusion GUI.
29
+ #
30
+ # Examples
31
+ #
32
+ # @deleter.delete
33
+ #
34
+ # Returns a Response with the result.
35
+ # If successful, the Response's data attribute will be nil.
36
+ # If there is an error, an unsuccessful Response will be returned.
37
+ def delete
38
+ unless @vm.exists?
39
+ return Response.new :code => 1, :message => 'VM does not exist'
40
+ end
41
+
42
+ running_response = @vm.running?
43
+ return running_response unless running_response.successful?
44
+
45
+ if running_response.data
46
+ message = 'The VM must not be running in order to delete it.'
47
+ return Response.new :code => 1, :message => message
48
+ end
49
+
50
+ FileUtils.rm_rf @vm.path
51
+ Metadata.delete_vm_info @vm.path
52
+
53
+ Response.new :code => 0
54
+ end
55
+
56
+ private
57
+ # Internal: Helper for getting the configured vmrun_cmd value.
58
+ #
59
+ # Examples
60
+ #
61
+ # @deleter.vmrun_cmd
62
+ # # => "/foo/bar/vmrun -T fusion"
63
+ #
64
+ # Returns a String for the configured value of
65
+ # Fission.config['vmrun_cmd'].
66
+ def vmrun_cmd
67
+ Fission.config['vmrun_cmd']
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,138 @@
1
+ module Fission
2
+ module Action
3
+ module VM
4
+
5
+ class Lister
6
+
7
+ # Public: Provides all of the VMs which are located in the VM directory.
8
+ #
9
+ # Examples
10
+ #
11
+ # Fission::VM.all.data
12
+ # # => [<Fission::VM:0x007fd6fa24c5d8 @name="foo">,
13
+ # <Fission::VM:0x007fd6fa23c5e8 @name="bar">]
14
+ #
15
+ # Returns a Response with the result.
16
+ # If successful, the Response's data attribute will be an Array of VM
17
+ # objects. If no VMs are found, the Response's data attribute will be an
18
+ # empty Array.
19
+ # If there is an error, an unsuccessful Response will be returned.
20
+ def all
21
+ vm_dirs = Dir[File.join Fission.config['vm_dir'], '*.vmwarevm'].select do |d|
22
+ File.directory? d
23
+ end
24
+
25
+ response = Response.new :code => 0
26
+ response.data = vm_dirs.collect do |dir|
27
+ Fission::VM.new(File.basename dir, '.vmwarevm')
28
+ end
29
+
30
+ response
31
+ end
32
+
33
+ # Public: Provides all of the VMs which are currently running.
34
+ #
35
+ # Examples
36
+ #
37
+ # Fission::VM.all_running.data
38
+ # # => [<Fission::VM:0x007fd6fa24c5d8 @name="foo">,
39
+ # <Fission::VM:0x007fd6fa23c5e8 @name="bar">]
40
+ #
41
+ # Returns a Response with the result.
42
+ # If successful, the Response's data attribute will be an Array of VM
43
+ # objects which are running. If no VMs are running, the Response's data
44
+ # attribute will be an empty Array.
45
+ # If there is an error, an unsuccessful Response will be returned.
46
+ def all_running
47
+ command = "#{Fission.config['vmrun_cmd']} list"
48
+ command_executor = Fission::Action::ShellExecutor.new command
49
+ result = command_executor.execute
50
+
51
+ response = Response.new :code => result['process_status'].exitstatus
52
+
53
+ if response.successful?
54
+ response.data = get_vm_objects_from_list_output result['output']
55
+ else
56
+ response.message = result['output']
57
+ end
58
+
59
+ response
60
+ end
61
+
62
+ # Public: Provides a list of all of the VMs and their associated status
63
+ #
64
+ # Examples
65
+ #
66
+ # Fission::VM.all_with_status.data
67
+ # # => { 'vm1' => 'running', 'vm2' => 'suspended', 'vm3' => 'not running'}
68
+ #
69
+ # Returns a Response with the result.
70
+ # If successful, the Response's data attribute will be a Hash of with the VM
71
+ # names as keys and their status as the values.
72
+ # If there is an error, an unsuccessful Repsonse will be returned.
73
+ def all_with_status
74
+ all_response = all
75
+ return all_response unless all_response.successful?
76
+
77
+ all_vms = all_response.data
78
+
79
+ running_response = all_running
80
+ return running_response unless running_response.successful?
81
+
82
+ response = Response.new :code => 0
83
+
84
+ @all_running_vm_names = running_response.data.collect { |v| v.name }
85
+
86
+ response.data = all_vms.inject({}) do |result, vm|
87
+ result[vm.name] = determine_status vm
88
+ result
89
+ end
90
+
91
+ response
92
+ end
93
+
94
+ private
95
+ # Internal: Helper to determines the status of a VM.
96
+ #
97
+ # vm - The VM object.
98
+ #
99
+ # Examples:
100
+ #
101
+ # @lister.determine_status my_vm
102
+ # # => 'suspended'
103
+ #
104
+ # Returns a String of the status.
105
+ def determine_status(vm)
106
+ return 'running' if @all_running_vm_names.include? vm.name
107
+ return 'suspended' if vm.suspend_file_exists?
108
+ return 'not running'
109
+ end
110
+
111
+ # Internal: Helper to get VM objects from the output of running VMs.
112
+ #
113
+ # output - output from the list command.
114
+ #
115
+ # Examples:
116
+ #
117
+ # @lister.get_vm_objects_from_list_output my_output
118
+ # # => [<Fission::VM:0x007fd6fa24c5d8 @name="foo">,
119
+ # <Fission::VM:0x007fd6fa23c5e8 @name="bar">]
120
+ #
121
+ # Returns an Array of VM objects.
122
+ def get_vm_objects_from_list_output(output)
123
+ vms = output.split("\n").select do |vm|
124
+ vm.include?('.vmx') &&
125
+ File.exists?(vm) &&
126
+ File.extname(vm) == '.vmx'
127
+ end
128
+
129
+ vms.collect do |vm|
130
+ Fission::VM.new File.basename(File.dirname(vm), '.vmwarevm')
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+ end
138
+ end