Class: Susi::VM

Inherits:
Object
  • Object
show all
Defined in:
lib/vm.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ VM



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/vm.rb', line 16

def initialize(name)
  @name = name
  @qmp = nil
  @qmp_port = nil
  @vnc_port = nil
  @ssh_port = nil

  Susi::debug "Searching for VM #{name}"

  ps = `ps aux | grep qemu-system-x86_64`
  Susi::debug ps
  ps.split("\n").each do |line|
    next unless line.match /\-name/
    m = line.match(/-name ([a-zA-Z0-9\-_\.]+)\s/)
    Susi::debug "match: #{m[1]}"
    n = m[1]
    next unless n == @name
    Susi::debug "Found VM #{n}"

    m = line.match(/-qmp tcp:localhost:(\d+)/)
    @qmp_port = m[1].to_i
    Susi::debug "Found QMP port #{@qmp_port}"
  end
  @qmp = QMP.new(@qmp_port)

  if block_given?
    yield(self)

    @qmp.close
  end
end

Class Method Details

.get_free_port(start_port, end_port) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/vm.rb', line 117

def self.get_free_port(start_port, end_port)
  (start_port..end_port).each do |port|
    begin
      server = TCPServer.new('127.0.0.1', port)
      server.close
      return port
    rescue Errno::EADDRINUSE
      next
    end
  end
  raise "No free port found in range #{start_port}..#{end_port}"
end

.install(name, disk, iso) ⇒ Object



113
114
115
# File 'lib/vm.rb', line 113

def self.install(name, disk, iso)
  self.start(name, disk, cdrom: iso)
end

.ls ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/vm.rb', line 186

def self.ls
  ps = `ps aux | grep qemu-system-x86_64`
  ps.split("\n").each do |line|
    next unless line.match /\-name/

    # get VM access
    m = line.match(/-name (\w+)/)
    n = m[1]
    VM.new(n) do |vm|
      # get local device name or IP
      ip = vm.ip

      shared_dir = line.match(/-fsdev.*path=([^\s,]+)/)
      shared_dir_info = shared_dir ? "  - Shared Directory: #{shared_dir[1]}" : ""

      puts "VM: \#{n}\n  - Disk: \#{vm.disk}\n  - QMP: \#{vm.qmp_port}\n  - VNC: vnc://\#{ip}:\#{vm.vnc_port}\n  - Screen: http://\#{ip}:\#{vm.vnc_www_port}/\n  - Websocket: ws://\#{ip}:\#{vm.vnc_websocket_port}/\n  - SSH: ssh -p \#{vm.ssh_port} dabo@\#{ip}\n\#{shared_dir_info}\n"
    end
  end
end

.quit(name) ⇒ Object



108
109
110
111
# File 'lib/vm.rb', line 108

def self.quit(name)
  vm = VM.new(name)
  vm.quit
end

.running?(name) ⇒ Boolean



5
6
7
8
9
10
11
12
13
14
# File 'lib/vm.rb', line 5

def self.running?(name)
  ps = `ps aux | grep qemu-system-x86_64`
  ps.split("\n").each do |line|
    next unless line.match /\-name/
    m = line.match(/-name (\w+)/)
    n = m[1]
    return true if n == name
  end
  return false
end

.start(name, disk, cdrom: nil, usb: nil, shared_dir: nil) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/vm.rb', line 130

def self.start(name, disk, cdrom: nil, usb: nil, shared_dir: nil)
  qmp_port = self.get_free_port(4000, 4099)
  ssh_port = self.get_free_port(2000, 2099)
  vnc_port = self.get_free_port(5900, 5999)
  vnc_www_port = vnc_port - 100
  vnc_websocket_port = vnc_port - 200

  Susi::debug "Starting VM #{name} with QMP on port #{qmp_port}, VNC on port #{vnc_port}, SSH on port #{ssh_port}"

  cmd = []

  cmd << "sudo"
  cmd << `which qemu-system-x86_64`.strip

  # general setup
  cmd << "-name #{name}"
  cmd << "-m 2048"
  cmd << "-hda #{File.expand_path(disk)}.qcow2"
  cmd << "-daemonize"
  cmd << "-enable-kvm"

  # Add shared directory passthrough if specified
  if shared_dir
    Susi::debug "Adding shared directory #{shared_dir}"
    shared_dir_path = File.expand_path(shared_dir)
    cmd << "-fsdev local,security_model=passthrough,id=fsdev0,path=#{shared_dir_path}"
    cmd << "-device virtio-9p-pci,fsdev=fsdev0,mount_tag=susi_virtio_share"
  end

  # control interfaces
  cmd << "-qmp tcp:localhost:#{qmp_port},server,nowait"
  cmd << "-vnc :#{vnc_port-5900},websocket=on"

  # Network capabilities
  cmd << "-nic user,model=virtio-net-pci,hostfwd=tcp::#{ssh_port}-:22"

  # cdrom for installation
  if cdrom
    cmd << "-cdrom #{cdrom}"
  end

  if usb
    cmd << "-device qemu-xhci"
    usb.each do |usbstr|
      vendor, product = usbstr.split(':')
      cmd << "-device usb-host,vendorid=0x#{vendor},productid=0x#{product}"
    end
  end

  cmd = cmd.join(" ")
  Susi::debug cmd
  system(cmd)

  QMP.new(qmp_port).close
end

Instance Method Details

#disk ⇒ Object



58
59
60
61
62
63
# File 'lib/vm.rb', line 58

def disk
  d = ''
  #QMP.new(@qmp_port) { |q| d = q.execute("query-block")[0]['inserted']['file'] }
  d = @qmp.execute("query-block")[0]['inserted']['file']
  d
end

#ip ⇒ Object



99
100
101
102
# File 'lib/vm.rb', line 99

def ip
  ip = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }
  ip.ip_address
end

#qmp_port ⇒ Object



104
105
106
# File 'lib/vm.rb', line 104

def qmp_port
  @qmp_port
end

#quit ⇒ Object



53
54
55
56
# File 'lib/vm.rb', line 53

def quit
  Susi::debug "Quitting QMP for VM #{@name}"
  @qmp.quit
end

#shutdown ⇒ Object



48
49
50
51
# File 'lib/vm.rb', line 48

def shutdown
  Susi::debug "Initiating shutdown for VM #{@name}"
  @qmp.execute("system_powerdown")
end

#ssh_port ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/vm.rb', line 82

def ssh_port
  @ssh_port ||= begin
    port = -1
    #QMP.new(@qmp_port) do |q|
      #resp = q.execute_with_args("human-monitor-command", {"command-line" => "info usernet"})
      resp = @qmp.execute_with_args("human-monitor-command", {"command-line" => "info usernet"})
      resp = resp.split("\r\n")[2].split
      src_port = resp[3].to_i
      dst_port = resp[5].to_i
      if dst_port == 22
        port = src_port
      end
    #end
    port.to_i
  end
end

#vnc_port ⇒ Object



65
66
67
68
69
70
71
72
# File 'lib/vm.rb', line 65

def vnc_port
  @vnc_port ||= begin
    port = -1
    #QMP.new(@qmp_port) { |q| port = q.execute("query-vnc")['service'] }
    port = @qmp.execute("query-vnc")['service']
    port.to_i
  end
end

#vnc_websocket_port ⇒ Object



78
79
80
# File 'lib/vm.rb', line 78

def vnc_websocket_port
  vnc_port - 200
end

#vnc_www_port ⇒ Object



74
75
76
# File 'lib/vm.rb', line 74

def vnc_www_port
  vnc_port - 100
end