diff --git a/Gemfile b/Gemfile index 0dd900f0..feb5f8a6 100644 --- a/Gemfile +++ b/Gemfile @@ -122,6 +122,12 @@ group :development do gem 'binding_of_caller' # Continous execution of unit tests gem 'guard' # https://github.com/guard/guard + # Benchmarking + # Supporting gem for Rails Panel (Google Chrome extension for Rails development) + gem 'meta_request' # https://github.com/dejan/rails_panel + # code profiler for MRI Ruby + gem 'ruby-prof' # https://github.com/ruby-prof/ruby-prof + end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 425c774d..0344e577 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -91,6 +91,7 @@ GEM activemodel (>= 5.0) builder (3.2.3) byebug (10.0.2) + callsite (0.0.11) capybara (3.13.2) addressable mini_mime (>= 0.1.3) @@ -177,6 +178,10 @@ GEM mini_mime (>= 0.1.1) marcel (0.3.3) mimemagic (~> 0.3.2) + meta_request (0.6.0) + callsite (~> 0.0, >= 0.0.11) + rack-contrib (>= 1.1, < 3) + railties (>= 3.0.0, < 6) method_source (0.9.2) mimemagic (0.3.3) mina (1.2.3) @@ -235,6 +240,8 @@ GEM pundit (2.0.1) activesupport (>= 3.0.0) rack (2.0.6) + rack-contrib (2.1.0) + rack (~> 2.0) rack-oauth2 (1.9.3) activesupport attr_required @@ -312,6 +319,7 @@ GEM unicode-display_width (~> 1.4.0) rubocop-rspec (1.32.0) rubocop (>= 0.60.0) + ruby-prof (0.17.0) ruby-progressbar (1.10.0) ruby_dep (1.5.0) sassc (2.0.0) @@ -416,6 +424,7 @@ DEPENDENCIES jquery-datatables jquery-rails listen (>= 3.0.5, < 3.2) + meta_request mina mina-logs mina-multistage @@ -433,6 +442,7 @@ DEPENDENCIES rspec-rails (~> 3.8) rubocop rubocop-rspec + ruby-prof sassc-rails select2-rails shoulda-matchers (= 4.0.0.rc1) diff --git a/app/api/v_sphere/folder.rb b/app/api/v_sphere/folder.rb index d08802de..775fe040 100644 --- a/app/api/v_sphere/folder.rb +++ b/app/api/v_sphere/folder.rb @@ -11,17 +11,24 @@ class Folder # see: https://code.vmware.com/apis/196/vsphere#/doc/vim.Folder.html#createFolder VSPHERE_FOLDER_NAME_CHARACTER_LIMIT = 79 - def initialize(rbvmomi_folder) + def initialize(rbvmomi_folder, parent: nil, name: nil) @folder = rbvmomi_folder + @parent = parent + @folder_name = name end - def parent - @folder.parent + def parent(lookup: true) + if @parent.nil? && lookup + parent = @folder.parent + @parent = parent.nil? ? nil : VSphere::Folder.new(parent) + else + @parent + end end def subfolders(recursive: false) folders = @folder.children.select { |folder_entry| folder_entry.is_a? RbVmomi::VIM::Folder }.map do |each| - Folder.new each + Folder.new each, parent: self end folders += folders.flat_map { |each| each.subfolders recursive: true } if recursive @@ -30,7 +37,7 @@ def subfolders(recursive: false) def vms(recursive: true) vms = @folder.children.select { |folder_entry| folder_entry.is_a? RbVmomi::VIM::VirtualMachine }.map do |each| - VSphere::VirtualMachine.new each + VSphere::VirtualMachine.new each, folder: self end vms += subfolders.flat_map(&:vms) if recursive @@ -53,14 +60,14 @@ def find_vm(name, recursive: true) end def name - @folder.name + @folder_name ||= @folder.name end # Ensure that a subfolder exists and return it # folder_name is a string with the name of the subfolder def ensure_subfolder(folder_name) subfolder = subfolders.find { |each| each.name == folder_name } - subfolder || VSphere::Folder.new(@folder.CreateFolder(name: folder_name)) + subfolder || VSphere::Folder.new(@folder.CreateFolder(name: folder_name), parent: self, name: folder_name) end # Ensure that the path relative to this folder is a valid folder and return it @@ -72,6 +79,7 @@ def ensure_subfolder_by_path(path) end def move_here(folder_entry) + folder_entry.parent_folder = self managed_entry = folder_entry.instance_exec { managed_folder_entry } @folder.MoveIntoFolder_Task(list: [managed_entry]).wait_for_completion end @@ -83,7 +91,11 @@ def create_vm(cpu, ram, capacity, name, cluster) vm_config = creation_config(cpu, ram, capacity, add_prefix(name), cluster.networks.first) vm = @folder.CreateVM_Task(config: vm_config, pool: cluster.resource_pool).wait_for_completion - VSphere::VirtualMachine.new vm + VSphere::VirtualMachine.new vm, folder: self + end + + def eql?(other) + (other.is_a? VSphere::Folder) && other.name == name end private diff --git a/app/api/v_sphere/virtual_machine.rb b/app/api/v_sphere/virtual_machine.rb index 14f2d4c2..19302c71 100644 --- a/app/api/v_sphere/virtual_machine.rb +++ b/app/api/v_sphere/virtual_machine.rb @@ -88,16 +88,21 @@ def self.user_vms(user) vms end - def initialize(rbvmomi_vm) + def initialize(rbvmomi_vm, folder: nil, name: nil) @vm = rbvmomi_vm + @folder = folder + @name = name end # handles name format YYYYMMDD_vm-name def name - if /^\d{8}_vm-/.match? @vm.name - @vm.name[12..-1] + return @name unless @name.nil? + + @vsphere_name ||= @vm.name + @name = if /^\d{8}_vm-/.match? @vsphere_name + @vsphere_name[12..-1] else - @vm.name + @vsphere_name end end @@ -112,11 +117,15 @@ def full_path end def parent_folder - root_folder.subfolders(recursive: true).find do |folder| + @folder ||= root_folder.subfolders(recursive: true).find do |folder| folder.vms(recursive: false).include? self end end + def parent_folder=(folder) + @folder = folder + end + # Guest OS communication def vm_ware_tools? tool_status = @vm&.guest&.toolsStatus @@ -147,11 +156,11 @@ def reboot_guest_os # We do not provide a power_state? method which just returns a boolean, because vSphere can internally handle # more than just two power states and we might later need to respond to more states than just two def powered_on? - @vm.summary.runtime.powerState == 'poweredOn' + summary.runtime.powerState == 'poweredOn' end def powered_off? - @vm.summary.runtime.powerState == 'poweredOff' + summary.runtime.powerState == 'poweredOff' end # Power state @@ -172,15 +181,15 @@ def change_power_state end # Archiving - # The archiving process actually just moves the VM into different folders to communicate their state + # The archiving process actually pending_revivings_folder.vms.any? { |vm| vm.equal? self }just moves the VM into different folders to communicate their state # Therefore we can check those folders to receive the current VM state # Archiving then moves a VM into the corresponding folder def pending_archivation? - pending_archivation_folder.vms.any? { |vm| vm.equal? self } + has_ancestor_folder 'Pending archivings' end def archived? - archived_folder.vms.any? { |vm| vm.equal? self } + has_ancestor_folder 'Archived VMs' end def set_pending_archivation @@ -212,7 +221,7 @@ def set_archived # Reviving def pending_reviving? - pending_revivings_folder.vms.any? { |vm| vm.equal? self } + has_ancestor_folder 'Pending revivings' end def set_pending_reviving @@ -229,7 +238,11 @@ def set_revived # Config methods # All the properties that HART saves internally def config - @config ||= VirtualMachineConfig.find_by_name name + unless @searched_config + @config = VirtualMachineConfig.find_by_name name + @searched_config = true + end + @config end def ensure_config @@ -266,11 +279,11 @@ def project # Information about the vm def boot_time - @vm.summary.runtime.bootTime + summary.runtime.bootTime end def summary - @vm.summary + @summary ||= @vm.summary end def macs @@ -290,7 +303,7 @@ def guest_heartbeat_status end def host_name - @vm.summary.runtime.host.name + summary.runtime.host.name end def active? @@ -357,6 +370,21 @@ def archivation_request ArchivationRequest.find_by_name(name) end + def has_ancestor_folder(ancestor_folder_name) + folder = parent_folder + return false if folder.nil? + + until folder.name == ancestor_folder_name + folder = folder.parent lookup: false + + if folder.nil? + return false + end + end + + true + end + def managed_folder_entry @vm end diff --git a/app/helpers/vms_helper.rb b/app/helpers/vms_helper.rb index 5a61c1ed..f916d6e8 100644 --- a/app/helpers/vms_helper.rb +++ b/app/helpers/vms_helper.rb @@ -31,6 +31,15 @@ def status_for(vm) end end + def status_color(vm) + status = vm.status + if status == :archived + 'bg-secondary' + else + status == :online ? 'bg-success' : 'bg-danger' + end + end + def notify_changed_users(old_list, new_list, sudo_lists, vm_name) removed_users = old_list - new_list removed_users.each do |user| diff --git a/app/views/vms/_table.erb b/app/views/vms/_table.erb index 52c9897d..05d1ec49 100644 --- a/app/views/vms/_table.erb +++ b/app/views/vms/_table.erb @@ -1,4 +1,4 @@ - <%= datatable_options.map{ |k,v| "data-#{k}=#{v}" }.join(' ') %>> +
<%= datatable_options.map{ |k,v| "data-#{k}=#{v}" }.join(' ') %>> @@ -17,7 +17,7 @@ <%= content_tag :div, nil, title: vm.status, :data => { toggle: 'tooltip', placement: 'right' }, - class: "round #{vm.archived? ? 'bg-secondary' : (vm.powered_on? ? 'bg-success' : 'bg-danger')}" %> + class: "round #{status_color vm}" %> <%# Allow also searching for description %> <% end %> <%# vms.each %> -
Status @@ -48,4 +48,4 @@
\ No newline at end of file + diff --git a/db/schema.rb b/db/schema.rb index 0bb93b81..0416e53d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -30,9 +30,9 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "vsphere_root_folder", default: "" - t.string "puppet_init_path", default: "/" - t.string "puppet_classes_path", default: "/Name" - t.string "puppet_nodes_path", default: "/Node" + t.string "puppet_init_path" + t.string "puppet_classes_path" + t.string "puppet_nodes_path" t.integer "min_cpu_cores" t.integer "max_cpu_cores" t.integer "max_ram_size" @@ -180,8 +180,6 @@ t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["request_id"], name: "index_users_assigned_to_requests_on_request_id" - t.index ["user_id"], name: "index_users_assigned_to_requests_on_user_id" end create_table "users_virtual_machine_configs", id: false, force: :cascade do |t| diff --git a/spec/api/v_sphere_api_mocker.rb b/spec/api/v_sphere_api_mocker.rb index 3e3a3d7f..db15ecb6 100644 --- a/spec/api/v_sphere_api_mocker.rb +++ b/spec/api/v_sphere_api_mocker.rb @@ -217,4 +217,4 @@ def v_sphere_connection_mock( end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength -# rubocop:enable RSpec/MessageChain +# rubocop:enable RSpec/MessageChain \ No newline at end of file diff --git a/spec/api/v_sphere_api_spec.rb b/spec/api/v_sphere_api_spec.rb index 90a29120..d46352a5 100644 --- a/spec/api/v_sphere_api_spec.rb +++ b/spec/api/v_sphere_api_spec.rb @@ -254,21 +254,21 @@ vm = VSphere::VirtualMachine.new mock_root_folder_vms.first allow(mock_archived_vms_folder).to receive_message_chain :MoveIntoFolder_Task, :wait_for_completion vm.set_archived - expect(mock_archived_vms_folder).to have_received(:MoveIntoFolder_Task) + expect(mock_archived_vms_folder).to have_received(:MoveIntoFolder_Task).at_least(:once) end it 'moves into the correct folder when it is pending_archivation' do vm = VSphere::VirtualMachine.new mock_root_folder_vms.first allow(mock_pending_archivings_folder).to receive_message_chain :MoveIntoFolder_Task, :wait_for_completion vm.set_pending_archivation - expect(mock_pending_archivings_folder).to have_received(:MoveIntoFolder_Task) + expect(mock_pending_archivings_folder).to have_received(:MoveIntoFolder_Task).at_least(:once) end it 'moves into the correct folder when it is pending_reviving' do vm = VSphere::VirtualMachine.new mock_root_folder_vms.first allow(mock_pending_revivings_folder).to receive_message_chain :MoveIntoFolder_Task, :wait_for_completion vm.set_pending_reviving - expect(mock_pending_revivings_folder).to have_received(:MoveIntoFolder_Task) + expect(mock_pending_revivings_folder).to have_received(:MoveIntoFolder_Task).at_least(:once) end it 'moves into the correct folder when it is revived' do