fission 0.3.0 → 0.4.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +1 -1
  4. data/lib/fission.rb +5 -6
  5. data/lib/fission/cli.rb +77 -7
  6. data/lib/fission/command.rb +43 -1
  7. data/lib/fission/command/clone.rb +19 -20
  8. data/lib/fission/command/delete.rb +29 -25
  9. data/lib/fission/command/snapshot_create.rb +11 -26
  10. data/lib/fission/command/snapshot_list.rb +13 -19
  11. data/lib/fission/command/snapshot_revert.rb +11 -26
  12. data/lib/fission/command/start.rb +11 -25
  13. data/lib/fission/command/status.rb +26 -10
  14. data/lib/fission/command/stop.rb +10 -21
  15. data/lib/fission/command/suspend.rb +21 -21
  16. data/lib/fission/command_helpers.rb +21 -0
  17. data/lib/fission/config.rb +35 -0
  18. data/lib/fission/fusion.rb +11 -3
  19. data/lib/fission/lease.rb +148 -0
  20. data/lib/fission/metadata.rb +55 -2
  21. data/lib/fission/response.rb +76 -0
  22. data/lib/fission/ui.rb +49 -0
  23. data/lib/fission/version.rb +1 -1
  24. data/lib/fission/vm.rb +653 -75
  25. data/spec/contexts/command.rb +12 -0
  26. data/spec/fission/cli_spec.rb +4 -11
  27. data/spec/fission/command/clone_spec.rb +45 -45
  28. data/spec/fission/command/delete_spec.rb +56 -43
  29. data/spec/fission/command/snapshot_create_spec.rb +29 -51
  30. data/spec/fission/command/snapshot_list_spec.rb +25 -26
  31. data/spec/fission/command/snapshot_revert_spec.rb +27 -53
  32. data/spec/fission/command/start_spec.rb +25 -69
  33. data/spec/fission/command/status_spec.rb +48 -13
  34. data/spec/fission/command/stop_spec.rb +25 -42
  35. data/spec/fission/command/suspend_spec.rb +54 -49
  36. data/spec/fission/command_helpers_spec.rb +30 -0
  37. data/spec/fission/command_spec.rb +19 -0
  38. data/spec/fission/config_spec.rb +24 -0
  39. data/spec/fission/fusion_spec.rb +6 -6
  40. data/spec/fission/lease_spec.rb +176 -0
  41. data/spec/fission/metadata_spec.rb +8 -8
  42. data/spec/fission/response_spec.rb +81 -0
  43. data/spec/fission/vm_spec.rb +869 -193
  44. data/spec/fission_spec.rb +0 -6
  45. data/spec/helpers/command_helpers.rb +12 -0
  46. data/spec/helpers/response_helpers.rb +21 -0
  47. data/spec/matchers/be_a_successful_response.rb +7 -0
  48. data/spec/matchers/be_an_unsuccessful_response.rb +10 -0
  49. data/spec/spec_helper.rb +7 -0
  50. metadata +24 -5
@@ -1,37 +1,31 @@
1
1
  module Fission
2
2
  class Command
3
3
  class SnapshotList < Command
4
-
5
- def initialize(args=[])
6
- super
7
- end
4
+ include Fission::CommandHelpers
8
5
 
9
6
  def execute
10
- unless @args.count == 1
11
- Fission.ui.output self.class.help
12
- Fission.ui.output ""
13
- Fission.ui.output_and_exit "Incorrect arguments for snapshot list command", 1
14
- end
7
+ incorrect_arguments 'snapshot list' unless @args.count == 1
15
8
 
16
- vm_name = @args.first
9
+ vm = VM.new @args.first
17
10
 
18
- unless Fission::VM.exists? vm_name
19
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
20
- end
11
+ response = vm.snapshots
21
12
 
22
- @vm = Fission::VM.new vm_name
23
- snaps = @vm.snapshots
13
+ if response.successful?
14
+ snaps = response.data
24
15
 
25
- if snaps.any?
26
- Fission.ui.output snaps.join("\n")
16
+ if snaps.any?
17
+ output snaps.join("\n")
18
+ else
19
+ output "No snapshots found for VM '#{vm.name}'"
20
+ end
27
21
  else
28
- Fission.ui.output "No snapshots found for VM '#{vm_name}'"
22
+ output_and_exit "There was an error listing the snapshots. The error was:\n#{response.message}", response.code
29
23
  end
30
24
  end
31
25
 
32
26
  def option_parser
33
27
  optparse = OptionParser.new do |opts|
34
- opts.banner = "\nsnapshot list: fission snapshot list my_vm"
28
+ opts.banner = "\nsnapshot list: fission snapshot list vm_name"
35
29
  end
36
30
 
37
31
  optparse
@@ -1,42 +1,27 @@
1
1
  module Fission
2
2
  class Command
3
3
  class SnapshotRevert < Command
4
-
5
- def initialize(args=[])
6
- super
7
- end
4
+ include Fission::CommandHelpers
8
5
 
9
6
  def execute
10
- unless @args.count == 2
11
- Fission.ui.output self.class.help
12
- Fission.ui.output ''
13
- Fission.ui.output_and_exit 'Incorrect arguments for snapshot revert command', 1
14
- end
15
-
16
- vm_name, snap_name = @args.take 2
7
+ incorrect_arguments 'snapshot revert' unless @args.count == 2
17
8
 
18
- unless Fission::VM.exists? vm_name
19
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
20
- end
21
-
22
- if Fission::Fusion.is_running?
23
- Fission.ui.output 'It looks like the Fusion GUI is currently running'
24
- Fission.ui.output_and_exit 'Please exit the Fusion GUI and try again', 1
25
- end
9
+ vm = VM.new @args[0]
10
+ snap_name = @args[1]
26
11
 
27
- @vm = Fission::VM.new vm_name
12
+ output "Reverting to snapshot '#{snap_name}'"
13
+ response = vm.revert_to_snapshot snap_name
28
14
 
29
- unless @vm.snapshots.include? snap_name
30
- Fission.ui.output_and_exit "Unable to find the snapshot '#{snap_name}'", 1
15
+ if response.successful?
16
+ output "Reverted to snapshot '#{snap_name}'"
17
+ else
18
+ output_and_exit "There was an error reverting to the snapshot. The error was:\n#{response.message}", response.code
31
19
  end
32
-
33
- Fission.ui.output "Reverting to snapshot '#{snap_name}'"
34
- @vm.revert_to_snapshot(snap_name)
35
20
  end
36
21
 
37
22
  def option_parser
38
23
  optparse = OptionParser.new do |opts|
39
- opts.banner = "\nsnapshot revert: fission snapshot revert my_vm snapshot_1"
24
+ opts.banner = "\nsnapshot revert: fission snapshot revert vm_name snapshot_1"
40
25
  end
41
26
 
42
27
  optparse
@@ -1,6 +1,7 @@
1
1
  module Fission
2
2
  class Command
3
3
  class Start < Command
4
+ include CommandHelpers
4
5
 
5
6
  def initialize(args=[])
6
7
  super
@@ -10,42 +11,27 @@ module Fission
10
11
  def execute
11
12
  option_parser.parse! @args
12
13
 
13
- if @args.empty?
14
- Fission.ui.output self.class.help
15
- Fission.ui.output ""
16
- Fission.ui.output_and_exit "Incorrect arguments for start command", 1
17
- end
14
+ incorrect_arguments 'start' if @args.empty?
18
15
 
19
- vm_name = @args.first
16
+ vm = VM.new @args.first
20
17
 
21
- unless Fission::VM.exists? vm_name
22
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
23
- end
18
+ output "Starting '#{vm.name}'"
19
+ start_args = {}
24
20
 
25
- if VM.all_running.include?(vm_name)
26
- Fission.ui.output ''
27
- Fission.ui.output_and_exit "VM '#{vm_name}' is already running", 0
28
- end
21
+ start_args[:headless] = true if @options.headless
29
22
 
30
- Fission.ui.output "Starting '#{vm_name}'"
31
- @vm = Fission::VM.new vm_name
23
+ response = vm.start start_args
32
24
 
33
- if @options.headless
34
- if Fission::Fusion.is_running?
35
- Fission.ui.output 'It looks like the Fusion GUI is currently running'
36
- Fission.ui.output 'A VM cannot be started in headless mode when the Fusion GUI is running'
37
- Fission.ui.output_and_exit "Exit the Fusion GUI and try again", 1
38
- else
39
- @vm.start :headless => true
40
- end
25
+ if response.successful?
26
+ output "VM '#{vm.name}' started"
41
27
  else
42
- @vm.start
28
+ output_and_exit "There was a problem starting the VM. The error was:\n#{response.message}", response.code
43
29
  end
44
30
  end
45
31
 
46
32
  def option_parser
47
33
  optparse = OptionParser.new do |opts|
48
- opts.banner = "\nstart usage: fission start vm [options]"
34
+ opts.banner = "\nstart usage: fission start vm_name [options]"
49
35
 
50
36
  opts.on '--headless', 'Start the VM in headless mode (i.e. no Fusion GUI console)' do
51
37
  @options.headless = true
@@ -2,19 +2,35 @@ module Fission
2
2
  class Command
3
3
  class Status < Command
4
4
 
5
- def initialize(args=[])
6
- super
7
- end
8
-
9
5
  def execute
10
- all_vms = VM.all
11
- all_running_vms = VM.all_running
6
+ response = VM.all
7
+ if response.successful?
8
+ all_vms = response.data
9
+ end
10
+
11
+ response = VM.all_running
12
+ if response.successful?
13
+ all_running_vm_names = response.data.collect { |v| v.name }
14
+ else
15
+ output_and_exit "There was an error getting the list of running VMs. The error was:\n#{response.message}", response.code
16
+ end
17
+
18
+ vm_names = all_vms.collect { |v| v.name }
19
+
20
+ longest_vm_name = vm_names.max { |a,b| a.length <=> b.length }
12
21
 
13
- longest_vm_name = all_vms.max { |a,b| a.length <=> b.length }
22
+ all_vms.each do |vm|
23
+ if all_running_vm_names.include? vm.name
24
+ status = '[running]'
25
+ else
26
+ if vm.suspend_file_exists?
27
+ status = '[suspended]'
28
+ else
29
+ status = '[not running]'
30
+ end
31
+ end
14
32
 
15
- VM.all.each do |vm|
16
- status = all_running_vms.include?(vm) ? '[running]' : '[not running]'
17
- Fission.ui.output_printf "%-#{longest_vm_name.length}s %s\n", vm, status
33
+ output_printf "%-#{longest_vm_name.length}s %s\n", vm.name, status
18
34
  end
19
35
  end
20
36
 
@@ -1,37 +1,26 @@
1
1
  module Fission
2
2
  class Command
3
3
  class Stop < Command
4
-
5
- def initialize(args=[])
6
- super
7
- end
4
+ include Fission::CommandHelpers
8
5
 
9
6
  def execute
10
- unless @args.count == 1
11
- Fission.ui.output self.class.help
12
- Fission.ui.output ""
13
- Fission.ui.output_and_exit "Incorrect arguments for stop command", 1
14
- end
7
+ incorrect_arguments 'stop' unless @args.count == 1
15
8
 
16
- vm_name = @args.first
9
+ vm = VM.new @args.first
17
10
 
18
- unless Fission::VM.exists? vm_name
19
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
20
- end
11
+ output "Stopping '#{vm.name}'"
12
+ response = vm.stop
21
13
 
22
- unless VM.all_running.include?(vm_name)
23
- Fission.ui.output ''
24
- Fission.ui.output_and_exit "VM '#{vm_name}' is not running", 0
14
+ if response.successful?
15
+ output "VM '#{vm.name}' stopped"
16
+ else
17
+ output_and_exit "There was an error stopping the VM. The error was:\n#{response.message}", response.code
25
18
  end
26
-
27
- Fission.ui.output "Stopping '#{vm_name}'"
28
- @vm = Fission::VM.new vm_name
29
- @vm.stop
30
19
  end
31
20
 
32
21
  def option_parser
33
22
  optparse = OptionParser.new do |opts|
34
- opts.banner = "\nstop usage: fission stop vm"
23
+ opts.banner = "\nstop usage: fission stop vm_name"
35
24
  end
36
25
 
37
26
  optparse
@@ -1,6 +1,7 @@
1
1
  module Fission
2
2
  class Command
3
3
  class Suspend < Command
4
+ include Fission::CommandHelpers
4
5
 
5
6
  def initialize(args=[])
6
7
  super
@@ -10,40 +11,39 @@ module Fission
10
11
  def execute
11
12
  option_parser.parse! @args
12
13
 
13
- if @args.count != 1 && !@options.all
14
- Fission.ui.output self.class.help
15
- Fission.ui.output ""
16
- Fission.ui.output_and_exit "Incorrect arguments for suspend command", 1
17
- end
14
+ incorrect_arguments 'suspend' if @args.count != 1 && !@options.all
15
+
16
+ vms_to_suspend.each do |vm|
17
+ output "Suspending '#{vm.name}'"
18
+ response = vm.suspend
18
19
 
19
- vms_to_suspend.each do |vm_name|
20
- Fission.ui.output "Suspending '#{vm_name}'"
21
- Fission::VM.new(vm_name).suspend
20
+ if response.successful?
21
+ output "VM '#{vm.name}' suspended"
22
+ else
23
+ output_and_exit "There was an error suspending the VM. The error was:\n#{response.message}", response.code
24
+ end
22
25
  end
23
26
  end
24
27
 
25
28
  def vms_to_suspend
26
29
  if @options.all
27
- vms_to_suspend = VM.all_running
28
- else
29
- vm_name = @args.first
30
- unless Fission::VM.exists? vm_name
31
- Fission.ui.output ''
32
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
33
- end
30
+ response = VM.all_running
34
31
 
35
- unless VM.all_running.include?(vm_name)
36
- Fission.ui.output ''
37
- Fission.ui.output_and_exit "VM '#{vm_name}' is not running", 1
32
+ if response.successful?
33
+ vms = response.data
34
+ else
35
+ output_and_exit "There was an error getting the list of running VMs. The error was:\n#{response.message}", response.code
38
36
  end
39
-
40
- vms_to_suspend = [vm_name]
37
+ else
38
+ vms = [ VM.new(@args.first) ]
41
39
  end
40
+
41
+ vms
42
42
  end
43
43
 
44
44
  def option_parser
45
45
  optparse = OptionParser.new do |opts|
46
- opts.banner = "\nsuspend usage: fission suspend [vm | --all]"
46
+ opts.banner = "\nsuspend usage: fission suspend [vm_name | --all]"
47
47
 
48
48
  opts.on '--all', 'Suspend all running VMs' do
49
49
  @options.all = true
@@ -0,0 +1,21 @@
1
+ module Fission
2
+ module CommandHelpers
3
+
4
+ # Internal: Outputs the help text for a command and exits.
5
+ #
6
+ # command_name - The name of the command to use in the output text.
7
+ #
8
+ # Examples
9
+ #
10
+ # incorrect_arguments 'delete'
11
+ #
12
+ # Returns nothing.
13
+ # This will call the help class method for the help text. This will exit
14
+ # with the exit code 1.
15
+ def incorrect_arguments(command_name)
16
+ output "#{self.class.help}\n"
17
+ output_and_exit "Incorrect arguments for #{command_name} command", 1
18
+ end
19
+
20
+ end
21
+ end
@@ -1,9 +1,21 @@
1
1
  module Fission
2
2
  class Config
3
+
4
+ # Public: Gets/Sets the Hash of attributes.
3
5
  attr_accessor :attributes
4
6
 
7
+ # Public: Path to the Fission conf file (default: ~/.fissionrc).
5
8
  CONF_FILE = File.expand_path '~/.fissionrc'
6
9
 
10
+ # Public: Initializes a Config object. This also sets the default config
11
+ # attributes for 'vmrun_bin', 'vmrun_cmd', 'vm_dir', 'plist_file', and
12
+ # 'gui_bin'.
13
+ #
14
+ # Examples
15
+ #
16
+ # Fission::Config.new
17
+ #
18
+ # Returns a new Config instance.
7
19
  def initialize
8
20
  @attributes = {}
9
21
  load_from_file
@@ -12,13 +24,36 @@ module Fission
12
24
  @attributes['vm_dir'] = File.expand_path('~/Documents/Virtual Machines.localized/')
13
25
  end
14
26
 
27
+ @attributes['lease_file'] = '/var/db/vmware/vmnet-dhcpd-vmnet8.leases'
15
28
  @attributes['vmrun_bin'] = '/Library/Application Support/VMware Fusion/vmrun'
16
29
  @attributes['vmrun_cmd'] = "#{@attributes['vmrun_bin'].gsub(' ', '\ ')} -T fusion"
17
30
  @attributes['plist_file'] = File.expand_path('~/Library/Preferences/com.vmware.fusion.plist')
18
31
  @attributes['gui_bin'] = File.expand_path('/Applications/VMware Fusion.app/Contents/MacOS/vmware')
19
32
  end
20
33
 
34
+ # Public: Helper method to access config atributes. This is a shortcut for
35
+ # querying the config attributes.
36
+ #
37
+ # item - The config item to query.
38
+ #
39
+ # Examples
40
+ #
41
+ # Fission.config['vmrun_bin']
42
+ # # => '/foo/bar/vmrun'
43
+ #
44
+ # Returns the value of the specified config item.
45
+ def [](item)
46
+ @attributes[item]
47
+ end
48
+
21
49
  private
50
+ # Internal: Loads config values from the Fission conf file into attributes.
51
+ #
52
+ # Examples
53
+ #
54
+ # load_from_file
55
+ #
56
+ # Returns nothing.
22
57
  def load_from_file
23
58
  if File.file?(CONF_FILE)
24
59
  @attributes.merge!(YAML.load_file(CONF_FILE))
@@ -1,12 +1,20 @@
1
1
  module Fission
2
2
  class Fusion
3
3
 
4
- def self.is_running?
4
+ # Public: Determines if the VMware Fusion GUI application is running.
5
+ #
6
+ # Examples
7
+ #
8
+ # Fission::Fusion.running?
9
+ # # => true
10
+ #
11
+ # Returns a Boolean.
12
+ def self.running?
5
13
  command = "ps -ef | grep -v grep | grep -c "
6
- command << "#{Fission.config.attributes['gui_bin'].gsub(' ', '\ ')} 2>&1"
14
+ command << "#{Fission.config['gui_bin'].gsub(' ', '\ ')} 2>&1"
7
15
  output = `#{command}`
8
16
 
9
- output.strip.to_i > 0 ? true : false
17
+ output.strip.to_i > 0
10
18
  end
11
19
 
12
20
  end
@@ -0,0 +1,148 @@
1
+ module Fission
2
+ class Lease
3
+
4
+ # Public: Get/set the IP address for the lease.
5
+ attr_accessor :ip_address
6
+
7
+ # Public: Get/set the MAC address for the lease.
8
+ attr_accessor :mac_address
9
+
10
+ # Public: Get/set the start DateTime for the lease.
11
+ attr_accessor :start
12
+
13
+ # Public: Get/set the end DateTime for the lease.
14
+ attr_accessor :end
15
+
16
+ # Public: Initialize a Lease object.
17
+ #
18
+ # args - Hash of arguments:
19
+ # :ip_address - String which denotes the IP address of the lease.
20
+ # :mac_address - String which denotes the MAC address of the lease.
21
+ # :start - DateTime which denotes the start of the lease.
22
+ # :end - DateTime which denotes the end of the lease.
23
+ #
24
+ # Examples
25
+ #
26
+ # Returns a new Lease instance.
27
+ def initialize(args={})
28
+ @ip_address = args[:ip_address]
29
+ @mac_address = args[:mac_address]
30
+ @start = args[:start]
31
+ @end = args[:end]
32
+ end
33
+
34
+ # Public: Determine if the lease has expired or not.
35
+ #
36
+ # Examples:
37
+ #
38
+ # @lease.expired?
39
+ # # => true
40
+ #
41
+ # Returns a Boolean. The Boolean is determined by comparing the end
42
+ # attribute to the current date/time.
43
+ def expired?
44
+ @end < DateTime.now
45
+ end
46
+
47
+ # Public: Provides all of the known leases.
48
+ #
49
+ # Examples:
50
+ #
51
+ # Fission::Lease.all
52
+ # # => [#<Fission::Lease:0x000001008b6d88...>,
53
+ # #<Fission::Lease:0x000001008522c0...>]
54
+ #
55
+ # Returns a Response with the result.
56
+ # If successful, the Response's data attribute will be an Array of Lease
57
+ # objects. If no leases are found, then the Response's data attribute will
58
+ # be an empty Array.
59
+ # If there is an error, an unsuccessful Response will be returned.
60
+ def self.all
61
+ response = Response.new
62
+
63
+ if File.file? Fission.config['lease_file']
64
+ content = File.read Fission.config['lease_file']
65
+
66
+ response.data = content.split('}').collect do |entry|
67
+ parse entry
68
+ end
69
+
70
+ content = nil
71
+
72
+ response.code = 0
73
+ else
74
+ response.code = 1
75
+ response.message = "Unable to find the lease file '#{Fission.config['lease_file']}'"
76
+ end
77
+
78
+ response
79
+ end
80
+
81
+ # Public: Get lease information for a specific MAC address.
82
+ #
83
+ # mac_address - MAC address (String) to search for.
84
+ #
85
+ # Examples
86
+ #
87
+ # Fission::Lease.find_by_mac '00:11:AA:bb:cc:22'
88
+ # # => #<Fission::Lease:0x000001008522c0...>
89
+ #
90
+ # Returns a Response with the result.
91
+ # If successful, the Response's data attribute will be a Lease object if the
92
+ # MAC address was found. The Response's data attribute will be nil if the
93
+ # MAC address was not found.
94
+ # If there is an error, an unsuccessful Response will be returned.
95
+ def self.find_by_mac_address(mac_address)
96
+ all_response = all
97
+
98
+ if all_response.successful?
99
+ response = Response.new :code => 0
100
+ response.data = all_response.data.find { |l| l.mac_address == mac_address }
101
+ else
102
+ response = all_response
103
+ end
104
+
105
+ response
106
+ end
107
+
108
+ private
109
+ # Internal: Parses information out of a DHCP lease entry.
110
+ #
111
+ # entry - String of lease entry text.
112
+ #
113
+ # Examples
114
+ #
115
+ # Lease.parse my_lease_entry
116
+ # # => #<Fission::Lease:0x000001008522c0...>
117
+ #
118
+ #
119
+ # Returns a Lease object which is populated with the attribute that were
120
+ # found.
121
+ def self.parse(lease_entry)
122
+ lease = Lease.new
123
+
124
+ lease_entry.gsub! ';', ''
125
+
126
+ lease_entry.split("\n").each do |line|
127
+ next if line =~ /^#/
128
+
129
+ line.strip!
130
+
131
+ case line.strip
132
+ when /^lease/
133
+ lease.ip_address = line.split(' ')[1]
134
+ when /^starts/
135
+ lease.start = DateTime.parse(line.split(' ')[2..3].join(' '))
136
+ when /^end/
137
+ lease.end = DateTime.parse(line.split(' ')[2..3].join(' '))
138
+ when /^hardware/
139
+ lease.mac_address = line.split(' ')[2]
140
+ end
141
+ end
142
+
143
+ lease
144
+
145
+ end
146
+
147
+ end
148
+ end