app/models/vm_or_template.rb
require 'ancestry'require 'ostruct'require 'cgi'require 'uri' class VmOrTemplate < ApplicationRecord include NewWithTypeStiMixin include RetirementMixin include ScanningMixin include SupportsFeatureMixin include SupportsAttribute include EmsRefreshMixin self.table_name = 'vms' has_ancestry include Operations include RetirementManagement include RightSizing include Scanning include Snapshotting attr_accessor :surrogate_host @surrogate_host = nil include ProviderObjectMixin include ComplianceMixin include OwnershipMixin include CustomAttributeMixin include EventMixin include ProcessTasksMixin include TenancyMixin include ManageIQ::Providers::Inflector::Methods VENDOR_TYPES = { # DB Displayed "azure" => "Azure", "azure_stack" => "AzureStack", "vmware" => "VMware", "microsoft" => "Microsoft", "xen" => "XenSource", "parallels" => "Parallels", "amazon" => "Amazon", "redhat" => "Red Hat", "ovirt" => "oVirt", "openstack" => "OpenStack", "openshift_infra" => "OpenShift Virtualization", "oracle" => "Oracle", "google" => "Google", "kubevirt" => "KubeVirt", "ibm_cloud" => "IBM Cloud", "ibm_power_vs" => "IBM Power Systems Virtual Server", "ibm_power_vc" => "IBM PowerVC", "ibm_power_hmc" => "IBM Power HMC", "ibm_z_vm" => "IBM Z/VM", "unknown" => "Unknown" } POWER_OPS = %w[start stop suspend reset shutdown_guest standby_guest reboot_guest] REMOTE_REGION_TASKS = POWER_OPS + %w[retire_now] validates_presence_of :name, :location validates :vendor, :inclusion => {:in => VENDOR_TYPES.keys} has_one :operating_system, :dependent => :destroy has_one :openscap_result, :as => :resource, :dependent => :destroy has_one :hardware, :dependent => :destroy has_one :miq_provision, :dependent => :nullify, :as => :destination has_one :miq_provision_template, :through => "miq_provision", :source => "source", :source_type => "VmOrTemplate" has_one :miq_server, :foreign_key => :vm_id, :inverse_of => :vm belongs_to :host belongs_to :ems_cluster belongs_to :cloud_tenant belongs_to :flavor belongs_to :placement_group belongs_to :storage belongs_to :storage_profile belongs_to :ext_management_system, :foreign_key => "ems_id", :inverse_of => :vms_and_templates belongs_to :resource_group belongs_to :tenant # Accounts - Users and Groups has_many :accounts, :dependent => :destroy has_many :users, -> { where(:accttype => 'user') }, :class_name => "Account" has_many :groups, -> { where(:accttype => 'group') }, :class_name => "Account" has_many :disks, :through => :hardware has_many :networks, :through => :hardware has_many :nics, :through => :hardware has_many :miq_provisions_from_template, :class_name => "MiqProvision", :as => :source, :dependent => :nullify has_many :miq_provision_vms, :through => :miq_provisions_from_template, :source => :destination, :source_type => "VmOrTemplate" has_many :miq_provision_requests, :as => :source has_many :guest_applications, :dependent => :destroy has_many :patches, :dependent => :destroy # System Services - Win32_Services, Kernel drivers, Filesystem drivers has_many :system_services, :dependent => :destroy has_many :win32_services, -> { where("typename = 'win32_service'") }, :class_name => "SystemService" has_many :kernel_drivers, -> { where("typename = 'kernel' OR typename = 'misc'") }, :class_name => "SystemService" has_many :filesystem_drivers, -> { where("typename = 'filesystem'") }, :class_name => "SystemService" has_many :linux_initprocesses, -> { where("typename = 'linux_initprocess' OR typename = 'linux_systemd'") }, :class_name => "SystemService" has_many :filesystems, :as => :resource, :dependent => :destroy has_many :directories, -> { where("rsc_type = 'dir'") }, :as => :resource, :class_name => "Filesystem" has_many :files, -> { where("rsc_type = 'file'") }, :as => :resource, :class_name => "Filesystem" has_many :scan_histories, :dependent => :destroy has_many :lifecycle_events, :class_name => "LifecycleEvent" has_many :advanced_settings, :as => :resource, :dependent => :destroy # Scan Items has_many :registry_items, :dependent => :destroy has_many :metrics, :as => :resource # Destroy will be handled by purger has_many :metric_rollups, :as => :resource # Destroy will be handled by purger has_many :vim_performance_states, :as => :resource # Destroy will be handled by purger has_many :storage_files, :dependent => :destroy has_many :storage_files_files, -> { where("rsc_type = 'file'") }, :class_name => "StorageFile" # EMS Events has_many :ems_events, ->(vmt) { unscope(:where => :vm_or_template_id).where(["vm_or_template_id = ? OR dest_vm_or_template_id = ?", vmt.id, vmt.id]).order(:timestamp) }, :class_name => "EmsEvent", :inverse_of => :vm_or_template has_many :ems_events_src, :class_name => "EmsEvent" has_many :ems_events_dest, :class_name => "EmsEvent", :foreign_key => :dest_vm_or_template_id has_many :policy_events, ->(vm) { where(["target_id = ? AND target_class = 'VmOrTemplate'", vm.id]).order(:timestamp) }, :foreign_key => "target_id" has_many :miq_events, :as => :target, :dependent => :destroy has_many :miq_alert_statuses, :dependent => :destroy, :as => :resource has_many :service_resources, :as => :resource has_many :direct_services, :through => :service_resources, :source => :service has_many :connected_shares, -> { where(:resource_type => "VmOrTemplate") }, :foreign_key => :resource_id, :class_name => "Share" has_many :labels, -> { where(:section => "labels") }, # rubocop:disable Rails/HasManyOrHasOneDependent :class_name => "CustomAttribute", :as => :resource, :inverse_of => :resource has_many :ems_custom_attributes, -> { where(:source => 'VC') }, # rubocop:disable Rails/HasManyOrHasOneDependent :class_name => "CustomAttribute", :as => :resource, :inverse_of => :resource has_many :counterparts, :as => :counterpart, :class_name => "ConfiguredSystem", :dependent => :nullify has_and_belongs_to_many :storages, :join_table => 'storages_vms_and_templates' acts_as_miq_taggable virtual_column :is_evm_appliance, :type => :boolean, :uses => :miq_server virtual_column :os_image_name, :type => :string, :uses => [:operating_system, :hardware] virtual_column :platform, :type => :string, :uses => [:operating_system, :hardware] virtual_column :product_name, :type => :string, :uses => [:operating_system] virtual_column :vendor_display, :type => :string virtual_column :v_owning_cluster, :type => :string, :uses => :ems_cluster virtual_column :v_owning_resource_pool, :type => :string, :uses => :all_relationships virtual_column :v_owning_datacenter, :type => :string, :uses => {:ems_cluster => :all_relationships} virtual_column :v_owning_folder, :type => :string, :uses => {:ems_cluster => :all_relationships} virtual_column :v_owning_folder_path, :type => :string, :uses => {:ems_cluster => :all_relationships} virtual_column :v_owning_blue_folder, :type => :string, :uses => :all_relationships virtual_column :v_owning_blue_folder_path, :type => :string, :uses => :all_relationships virtual_column :v_datastore_path, :type => :string, :uses => :storage virtual_column :v_parent_blue_folder_display_path, :type => :string, :uses => :all_relationships virtual_column :thin_provisioned, :type => :boolean, :uses => {:hardware => :disks} virtual_column :used_storage, :type => :integer, :uses => [:used_disk_storage, :mem_cpu] virtual_column :used_storage_by_state, :type => :integer, :uses => :used_storage virtual_column :uncommitted_storage, :type => :integer, :uses => [:provisioned_storage, :used_storage_by_state] virtual_column :ipaddresses, :type => :string_set, :uses => {:hardware => :ipaddresses} virtual_column :hostnames, :type => :string_set, :uses => {:hardware => :hostnames} virtual_column :mac_addresses, :type => :string_set, :uses => {:hardware => :mac_addresses} virtual_column :memory_exceeds_current_host_headroom, :type => :string, :uses => [:mem_cpu, {:host => [:hardware, :ext_management_system]}] virtual_column :has_rdm_disk, :type => :boolean, :uses => {:hardware => :disks} virtual_column :disks_aligned, :type => :string, :uses => {:hardware => {:hard_disks => :partitions_aligned}} virtual_has_many :processes, :class_name => "OsProcess", :uses => {:operating_system => :processes} virtual_has_many :event_logs, :uses => {:operating_system => :event_logs} virtual_has_many :lans, :uses => {:hardware => {:nics => :lan}} virtual_has_many :child_resources, :class_name => "VmOrTemplate" virtual_belongs_to :parent_resource_pool, :class_name => "ResourcePool", :uses => :all_relationships virtual_has_one :direct_service, :class_name => 'Service' virtual_has_one :service, :class_name => 'Service' virtual_has_one :parent_resource, :class_name => "VmOrTemplate" virtual_delegate :name, :to => :host, :prefix => true, :allow_nil => true, :type => :string virtual_delegate :name, :to => :storage, :prefix => true, :allow_nil => true, :type => :string virtual_delegate :name, :to => :ems_cluster, :prefix => true, :allow_nil => true, :type => :string virtual_delegate :vmm_product, :to => :host, :prefix => :v_host, :allow_nil => true, :type => :string virtual_delegate :v_pct_free_disk_space, :v_pct_used_disk_space, :to => :hardware, :allow_nil => true, :type => :float virtual_delegate :num_cpu, :to => "hardware.cpu_sockets", :allow_nil => true, :default => 0, :type => :integer virtual_delegate :cpu_total_cores, :cpu_cores_per_socket, :to => :hardware, :allow_nil => true, :default => 0, :type => :integer virtual_delegate :annotation, :to => :hardware, :prefix => "v", :allow_nil => true, :type => :string virtual_delegate :ram_size_in_bytes, :to => :hardware, :allow_nil => true, :default => 0, :type => :integer virtual_delegate :mem_cpu, :to => "hardware.memory_mb", :allow_nil => true, :default => 0, :type => :integer virtual_delegate :ram_size, :to => "hardware.memory_mb", :allow_nil => true, :default => 0, :type => :integer delegate :connect_lans, :disconnect_lans, :to => :hardware, :allow_nil => true delegate :queue_name_for_ems_operations, :to => :ext_management_system, :allow_nil => true supports_attribute :feature => :reconfigure_disks supports_attribute :feature => :reconfigure_disksize supports_attribute :feature => :reconfigure_cdroms supports_attribute :feature => :reconfigure_network_adapters after_save :save_genealogy_information scope :active, -> { where.not(:ems_id => nil) } scope :with_type, ->(type) { where(:type => type) } scope :archived, -> { where(:ems_id => nil, :storage_id => nil) } scope :orphaned, -> { where(:ems_id => nil).where.not(:storage_id => nil) } scope :retired, -> { where(:retired => true) } scope :not_active, -> { where(:ems_id => nil) } scope :not_archived, -> { where.not(:ems_id => nil).or(where.not(:storage_id => nil)) } scope :not_orphaned, -> { where.not(:ems_id => nil).or(where(:storage_id => nil)) } scope :not_retired, -> { where(:retired => false).or(where(:retired => nil)) } scope :from_cloud_managers, -> { where(:ext_management_system => ManageIQ::Providers::CloudManager.all) } scope :from_infra_managers, -> { where(:ext_management_system => ManageIQ::Providers::InfraManager.all) } def from_cloud_manager? ext_management_system&.kind_of?(ManageIQ::Providers::CloudManager) end def from_infra_manager? ext_management_system&.kind_of?(ManageIQ::Providers::InfraManager) end # The SQL form of `#registered?`, with its inverse as well. # TODO: Vmware Specific (copied (old) TODO from #registered?) scope :registered, (lambda do where(arel_table[:template].eq(false).or(arel_table[:ems_id].not_eq(nil)).and(arel_table[:host_id].not_eq(nil))) end) scope :unregistered, (lambda do where(arel_table[:template].eq(true).and(arel_table[:ems_id].eq(nil)).or(arel_table[:host_id].eq(nil))) end) alias_method :datastores, :storages # Used by web-services to return datastores as the property name alias_method :parent_cluster, :ems_cluster alias_method :owning_cluster, :ems_cluster # Add virtual columns/methods for specific things derived from advanced_settings REQUIRED_ADVANCED_SETTINGS = { 'vmi.present' => [:paravirtualization, :boolean], 'vmsafe.enable' => [:vmsafe_enable, :boolean], 'vmsafe.agentAddress' => [:vmsafe_agent_address, :string], 'vmsafe.agentPort' => [:vmsafe_agent_port, :integer], 'vmsafe.failOpen' => [:vmsafe_fail_open, :boolean], 'vmsafe.immutableVM' => [:vmsafe_immutable_vm, :boolean], 'vmsafe.timeoutMS' => [:vmsafe_timeout_ms, :integer], 'entitled_processors' => [:entitled_processors, :float], 'processor_type' => [:processor_share_type, :string], 'pin_policy' => [:processor_pin_policy, :string], 'software_licenses' => [:software_licenses, :string], } REQUIRED_ADVANCED_SETTINGS.each do |k, (m, t)| define_method(m) do as = advanced_settings.detect { |setting| setting.name == k } return nil if as.nil? || as.value.nil? case t when :boolean then ActiveRecord::Type::Boolean.new.cast(as.value) when :integer then as.value.to_i when :float then as.value.to_f else as.value.to_s end end virtual_column m, :type => t, :uses => :advanced_settings end # Add virtual columns/methods for details about each disk (1..9).each do |i| disk_methods = [ ['disk_type', :string], ['mode', :string], ['size', :integer], ['size_on_disk', :integer], ['used_percent_of_provisioned', :float], ['partitions_aligned', :string] ] disk_methods.each do |k, t| m = "disk_#{i}_#{k}".to_sym define_method(m) do return nil if hardware.nil? return nil if hardware.hard_disks.length < i hardware.hard_disks[i - 1].send(k) end virtual_column m, :type => t, :uses => {:hardware => :hard_disks} end end # Add virtual columns/methods for accessing individual folders in a path (1..9).each do |i| m = "parent_blue_folder_#{i}_name".to_sym define_method(m) do f = parent_blue_folders(:exclude_root_folder => true, :exclude_non_display_folders => true)[i - 1] f.nil? ? "" : f.name end virtual_column m, :type => :string, :uses => :all_relationships end include RelationshipMixin self.default_relationship_type = "genealogy" self.skip_relationships += ["genealogy"] include MiqPolicyMixin include AlertMixin include DriftStateMixin include UuidMixin include Metric::CiMixin include FilterableMixin include StorageMixin def self.manager_class if module_parent == Object ExtManagementSystem else module_parent end end def self.model_suffix manager_class.short_token end def to_s name end def is_evm_appliance? !!miq_server end alias_method :is_evm_appliance, :is_evm_appliance? # Determines if the VM is on an EMS or Host def registered? # TODO: Vmware specific return false if template? && ems_id.nil? host_id.present? end # TODO: Vmware specific, and is this even being used anywhere? def connected_to_ems? connection_state == 'connected' || connection_state.nil? end def terminated? current_state == 'terminated' end def makesmart(_options = {}) self.smart = true save end def run_command_via_parent(verb, options = {}) unless ext_management_system raise _("VM/Template <%{name}> with Id: <%{id}> is not associated with a provider.") % {:name => name, :id => id} end unless ext_management_system.authentication_status_ok? raise _("VM/Template <%{name}> with Id: <%{id}>: Provider authentication failed.") % {:name => name, :id => id} end # TODO: Need to break this logic out into a method that can look at the verb and the vm and decide the best way to invoke it - Virtual Center WS, ESX WS, Storage Proxy. _log.info("Invoking [#{verb}] through EMS: [#{ext_management_system.name}]") options = {:user_event => "Console Request Action [#{verb}], VM [#{name}]"}.merge(options) ext_management_system.send(verb, self, options) end def run_command_via_task(task_options, queue_options) MiqTask.generic_action_with_callback(task_options, command_queue_options(queue_options)) end def run_command_via_queue(method_name, queue_options = {}) queue_options[:method_name] = method_name MiqQueue.put(command_queue_options(queue_options)) end def make_retire_request(requester_id) self.class.make_retire_request(id, User.find(requester_id)) end # keep the same method signature as others in retirement mixin def self.make_retire_request(*src_ids, requester, initiated_by: 'user') vms = where(:id => src_ids) missing_ids = src_ids - vms.pluck(:id) _log.error("Retirement of [Vm] IDs: [#{missing_ids.join(', ')}] skipped - target(s) does not exist") if missing_ids.present? vms.each do |target| target.check_policy_prevent('request_vm_retire', "retire_request_after_policy_check", requester.userid, :initiated_by => initiated_by) end end def retire_request_after_policy_check(userid, initiated_by: 'user') options = {:src_ids => [id], :__initiated_by__ => initiated_by, :__request_type__ => VmRetireRequest.request_types.first} requester = User.find_by(:userid => userid) self.class.set_retirement_requester(options[:src_ids], requester) VmRetireRequest.make_request(nil, options, requester) end # policy_event: the event sent to automate for policy resolution # cb_method: the MiqQueue callback method along with the parameters that is called # when automate process is done and the event is not prevented to proceed by policy def check_policy_prevent(policy_event, *cb_method) enforce_policy(policy_event, {}, {:miq_callback => prevent_callback_settings(*cb_method)}) unless policy_event.nil? end def enforce_policy(event, inputs = {}, options = {}) return {"result" => true, :details => []} if event.to_s == "rsop" && host.nil? raise _("vm does not belong to any host") if host.nil? && ext_management_system.nil? inputs[:vm] = self inputs[:host] = host unless host.nil? inputs[:ext_management_system] = ext_management_system unless ext_management_system.nil? MiqEvent.raise_evm_event(self, event, inputs, options) end # override def self.validate_task(task, vm, options) return false unless super return false if options[:task] == "destroy" || options[:task] == "check_compliance_queue" return false if vm.has_required_host? # VM has no host or storage affiliation if vm.storage.nil? task.error("#{vm.name}: There is no owning Host or Datastore for this VM, " \ "'#{options[:task]}' is not allowed") return false end # VM belongs to a storage/repository location # TODO: The following never gets run since the invoke tasks invokes it as a job, and only tasks get to this point ? unless %w[scan sync].include?(options[:task]) task.error("#{vm.name}: There is no owning Host for this VM, '#{options[:task]}' is not allowed") return false end spid = ::Settings.repository_scanning.defaultsmartproxy if spid.nil? # No repo scanning SmartProxy configured task.error("#{vm.name}: No Default Repository SmartProxy is configured, contact your EVM administrator") return false elsif MiqProxy.exists?(spid) == false task.error("#{vm.name}: The Default Repository SmartProxy no longer exists, contact your EVM Administrator") return false end if MiqProxy.find(spid).state != "on" # Repo scanning host iagent s not running task.error("#{vm.name}: The Default Repository SmartProxy, '#{sp.name}', is not running. " \ "'#{options[:task]}' not attempted") return false end true end private_class_method :validate_task # override def self.task_invoked_by(options) %w[scan sync].include?(options[:task]) ? :job : super end private_class_method :task_invoked_by # override def self.task_arguments(options) case options[:task] when "scan", "sync" [options[:userid]] when "remove_snapshot", "revert_to_snapshot" [options[:snap_selected]] when "create_snapshot" [options[:name], options[:description], options[:memory]] else super end end private_class_method :task_arguments def powerops_callback(task_id, status, msg, result, _queue_item) task = MiqTask.find_by(:id => task_id) task.queue_callback("Finished", status, msg, result) if task end # override def self.invoke_task_local(task, vm, options, args) user = User.current_user cb = nil if task cb = if POWER_OPS.include?(options[:task]) { :class_name => vm.class.base_class.name, :instance_id => vm.id, :method_name => :powerops_callback, :args => [task.id] } else { :class_name => task.class.to_s, :instance_id => task.id, :method_name => :queue_callback, :args => ["Finished"] } end end q_hash = if options[:task] == "destroy" { :class_name => base_class.name, :instance_id => vm.id, :method_name => options[:task], :args => args, :miq_task_id => task&.id, :miq_callback => cb, } else { :service => options[:invoke_by] == :job ? "smartstate" : "ems_operations", :affinity => vm.ext_management_system, :class_name => base_class.name, :instance_id => vm.id, :method_name => options[:task], :args => args, :miq_task_id => task&.id, :miq_callback => cb, } end q_hash.merge!(:user_id => user.id, :group_id => user.current_group.id, :tenant_id => user.current_tenant.id) if user MiqQueue.submit_job(q_hash) end def self.action_for_task(task) case task when "retire_now" "retire" else task end end def scan_data_current? !(last_scan_on.nil? || last_scan_on > last_sync_on) end def genealogy_parent with_relationship_type("genealogy") { parent } end def genealogy_parent=(parent) with_relationship_type('genealogy') do if use_ancestry? self.parent = parent else @genealogy_parent_object = parent end end end # save_genealogy_information is only necessary for relationships using genealogy # when using ancestry, the relationship will be saved after the fact # when not using ancestry, the relationship is saved on assignment, necessitating the prior save of the vm/template record # this variable is used to delay that assignment def save_genealogy_information if defined?(@genealogy_parent_object) && @genealogy_parent_object with_relationship_type('genealogy') { self.parent = @genealogy_parent_object } end end def os_image_name name = OperatingSystem.image_name(self) if name == 'unknown' parent = genealogy_parent name = OperatingSystem.image_name(parent) unless parent.nil? end name end def platform name = OperatingSystem.platform(self) if name == 'unknown' parent = genealogy_parent name = OperatingSystem.platform(parent) unless parent.nil? end name end def product_name name = try(:operating_system).try(:product_name) name ||= genealogy_parent.try(:operating_system).try(:product_name) name ||= "" name end def service_pack name = try(:operating_system).try(:service_pack) name ||= genealogy_parent.try(:operating_system).try(:service_pack) name ||= "" name end def vendor_display VENDOR_TYPES[vendor] end # # Path/location methods # # TODO: Vmware specific URI methods? Next 3 methods def self.location2uri(location, scheme = "file") pat = %r{^(file|http|miq)://([^/]*)/(.+)$} unless pat&.match?(location) # location = scheme<<"://"<<self.myhost.ipaddress<<":1139/"<<location location = scheme << ":///" << location end location end def save_scan_history(datahash) result = scan_histories.build( :status => datahash['status'], :status_code => datahash['status_code'].to_i, :message => datahash['message'], :started_on => Time.parse(datahash['start_time']), :finished_on => Time.parse(datahash['end_time']), :task_id => datahash['taskid'] ) self.last_scan_on = Time.parse(datahash['start_time']) save result end Method `repository_parse_path` has a Cognitive Complexity of 13 (exceeds 11 allowed). Consider refactoring. def self.repository_parse_path(path) path.tr!("\\", "/") # it's empty string for local type storage_name = "" # NAS relative_path = if path.starts_with?("//") raise _("path, '%{path}', is malformed") % {:path => path} unless %r{^//[^/].*/.+$}.match?(path) # path is a UNC storage_name = path.split("/")[0..3].join("/") path.split("/")[4..path.length].join("/") if path.length > 4 # VMFS elsif path.starts_with?("[") raise _("path, '%{path}', is malformed") % {:path => path} unless /^\[[^\]].+\].*$/.match?(path) # path is a VMWare storage name /^\[(.*)\](.*)$/ =~ path storage_name = $1 temp_path = $2.strip # Some esx servers add a leading "/". # This needs to be stripped off to allow matching on location temp_path.sub(/^\//, '') # local else raise _("path, '%{path}', is malformed") % {:path => path} end return storage_name, (relative_path.empty? ? "/" : relative_path) end # # Relationship methods # def disconnect_inv disconnect_storage disconnect_ems classify_with_parent_folder_path(false) with_relationship_type('ems_metadata') do remove_all_parents(:of_type => ['EmsFolder', 'ResourcePool']) end disconnect_host disconnect_stack if respond_to?(:orchestration_stack) end def disconnect_stack(stack = nil) return unless orchestration_stack return if stack && stack != orchestration_stack log_text = " from stack [#{orchestration_stack.name}] id [#{orchestration_stack.id}]" _log.info("Disconnecting Vm [#{name}] id [#{id}]#{log_text}") self.orchestration_stack = nil save end Similar blocks of code found in 2 locations. Consider refactoring. def connect_ems(e) unless ext_management_system == e _log.debug("Connecting Vm [#{name}] id [#{id}] to EMS [#{e.name}] id [#{e.id}]") self.ext_management_system = e save end end Similar blocks of code found in 2 locations. Consider refactoring. def disconnect_ems(e = nil) if e.nil? || ext_management_system == e log_text = " from EMS [#{ext_management_system.name}] id [#{ext_management_system.id}]" unless ext_management_system.nil? _log.info("Disconnecting Vm [#{name}] id [#{id}]#{log_text}") self.ext_management_system = nil self.ems_cluster = nil self.raw_power_state = "unknown" save end end def connect_host(h) unless host == h _log.debug("Connecting Vm [#{name}] id [#{id}] to Host [#{h.name}] id [#{h.id}]") self.host = h save # Also connect any nics to their lans connect_lans(h.lans) end end def disconnect_host(h = nil) if h.nil? || host == h log_text = " from Host [#{host.name}] id [#{host.id}]" unless host.nil? _log.info("Disconnecting Vm [#{name}] id [#{id}]#{log_text}") self.host = nil save # Also disconnect any nics from their lans disconnect_lans end end Similar blocks of code found in 2 locations. Consider refactoring. def connect_storage(s) unless storage == s _log.debug("Connecting Vm [#{name}] id [#{id}] to Datastore [#{s.name}] id [#{s.id}]") self.storage = s save end end def disconnect_storage(s = nil) if s.nil? || storage == s || storages.include?(s) stores = s.nil? ? ([storage] + storages).compact.uniq : [s] log_text = stores.collect { |x| "Datastore [#{x.name}] id [#{x.id}]" }.join(", ") _log.info("Disconnecting Vm [#{name}] id [#{id}] from #{log_text}") if s.nil? self.storage = nil self.storages = [] else self.storage = nil if storage == s storages.delete(s) end save end end # Parent rp, folder and dc methods # TODO: Replace all with ancestors lookup once multiple parents is sorted out def parent_resource_pool with_relationship_type('ems_metadata') do parent(:of_type => "ResourcePool") end end alias_method :owning_resource_pool, :parent_resource_pool def parent_blue_folder with_relationship_type('ems_metadata') do parent(:of_type => "EmsFolder") end end alias_method :owning_blue_folder, :parent_blue_folder def parent_blue_folders(*args) f = parent_blue_folder f.nil? ? [] : f.folder_path_objs(*args) end def under_blue_folder?(folder) return false unless folder.kind_of?(EmsFolder) Use `any?(folder)` instead of block. parent_blue_folders.any? { |f| f == folder } end def parent_blue_folder_path(*args) f = parent_blue_folder f.nil? ? "" : f.folder_path(*args) end alias_method :owning_blue_folder_path, :parent_blue_folder_path def parent_folder ems_cluster.try(:parent_folder) end alias_method :owning_folder, :parent_folder alias_method :parent_yellow_folder, :parent_folder def parent_folders(*args) f = parent_folder f.nil? ? [] : f.folder_path_objs(*args) end alias_method :parent_yellow_folders, :parent_folders def parent_folder_path(*args) f = parent_folder f.nil? ? "" : f.folder_path(*args) end alias_method :owning_folder_path, :parent_folder_path alias_method :parent_yellow_folder_path, :parent_folder_path def parent_datacenter ems_cluster.try(:parent_datacenter) end alias_method :owning_datacenter, :parent_datacenter def parent_blue_folder_display_path parent_blue_folder_path(:exclude_non_display_folders => true) end alias_method :v_parent_blue_folder_display_path, :parent_blue_folder_display_path def lansUse `filter_map` instead. !hardware.nil? ? hardware.nics.collect(&:lan).compact : [] end # Create a hash of this Vm's EMS and Host and their credentials def ems_host_list params = {} [ext_management_system, "ems", host, "host"].each_slice(2) do |ems, type| if ems params[type] = { :hostname => ems.hostname, :ipaddress => ems.ipaddress, :username => ems.authentication_userid, :password => ems.authentication_password_encrypted, :class_name => ems.class.name } params[type][:port] = ems.port if ems.respond_to?(:port) && ems.port.present? end end params end def reconnect_events events = EmsEvent.where("ems_id = ? AND ((vm_ems_ref = ? AND vm_or_template_id IS NULL) OR (dest_vm_ems_ref = ? AND dest_vm_or_template_id IS NULL))", ext_management_system.id, ems_ref, ems_ref) events.each do |e| do_save = false src_vm = e.src_vm_or_template if src_vm.nil? && e.vm_ems_ref == ems_ref src_vm = self e.vm_or_template_id = src_vm.id e.vm_name = src_vm.name do_save = true end dest_vm = e.dest_vm_or_template if dest_vm.nil? && e.dest_vm_ems_ref == ems_ref dest_vm = self e.dest_vm_or_template_id = dest_vm.id do_save = true end e.save if do_save # Hook up genealogy after a Clone Task src_vm.add_genealogy_child(dest_vm) if src_vm && dest_vm && e.event_type == EmsEvent::CLONE_TASK_COMPLETE end true end def add_genealogy_child(child) with_relationship_type('genealogy') do set_child(child) end end def myhost return @surrogate_host if @surrogate_host return host unless host.nil? self.class.proxy_host_for_repository_scans end def self.scan_via_ems? !::Settings.coresident_miqproxy.scan_via_host end delegate :scan_via_ems?, :to => :class # Cache the proxy host for repository scans because the JobProxyDispatch calls this for each Vm scan job in a loop cache_with_timeout(:proxy_host_for_repository_scans, 30.seconds) do defaultsmartproxy = ::Settings.repository_scanning.defaultsmartproxy proxy = nil proxy = MiqProxy.find_by(:id => defaultsmartproxy.to_i) if defaultsmartproxy proxy.try(:host) end def my_zone ems = ext_management_system ems ? ems.my_zone : MiqServer.my_zone end def my_zone_obj Zone.find_by(:name => my_zone) end # # Proxy methods # # TODO: Come back to this def proxies4job(_job = nil) _log.debug("Enter") all_proxy_list = storage2proxies proxies = storage2active_proxies(all_proxy_list) _log.debug("# proxies = #{proxies.length}") msg = if all_proxy_list.empty? "No active SmartProxies found to analyze this VM" elsif proxies.empty? "Provide credentials for this VM's Host to perform SmartState Analysis" else 'Perform SmartState Analysis on this VM' end log_all_proxies(all_proxy_list, msg) if proxies.empty? {:proxies => proxies.flatten, :message => msg} end def log_all_proxies(all_proxy_list, message) proxies = all_proxy_list.collect { |a| "[#{log_proxies_format_instance(a)}]" } proxies_text = proxies.empty? ? "[none]" : proxies.join(" -- ") _log.warn("Proxies for #{log_proxies_vm_config} : #{proxies_text}") _log.warn("Proxies message: #{message}") end def log_proxies_vm_config msg = "[#{log_proxies_format_instance(self)}] on host [#{log_proxies_format_instance(host)}] datastore " msg << (storage ? "[#{storage.name}-#{storage.store_type}]" : "No storage") end def log_proxies_format_instance(object) return 'Nil' if object.nil? "#{object.class.name}:#{object.id}-#{object.name}:#{object.try(:state)}" end def storage2proxies @storage_proxies ||= begin # Support vixDisk scanning of VMware VMs from the vmdb server miq_server_proxies end end def storage2active_proxies(all_proxy_list = nil) all_proxy_list ||= storage2proxies _log.debug("all_proxy_list.length = #{all_proxy_list.length}") proxies = all_proxy_list.select(&:is_proxy_active?) _log.debug("proxies1.length = #{proxies.length}") # MiqServer coresident proxy needs to contact the host and provide credentials. # Remove any MiqServer instances if we do not have credentials rsc = scan_via_ems? ? ext_management_system : host proxies.delete_if { |p| p.is_a?(MiqServer) } if rsc && !rsc.authentication_status_ok? _log.debug("proxies2.length = #{proxies.length}") proxies end def has_active_proxy? storage2active_proxies.any? end def has_proxy? storage2proxies.any? end # Cache the servers because the JobProxyDispatch calls this for each Vm scan job in a loop cache_with_timeout(:miq_servers_for_scan, 30.seconds) do MiqServer.where(:status => "started").includes([:zone, :server_roles]).to_a end Cyclomatic complexity for miq_server_proxies is too high. [22/11]
Method `miq_server_proxies` has a Cognitive Complexity of 18 (exceeds 11 allowed). Consider refactoring. def miq_server_proxies case vendor when 'vmware' # VM cannot be scanned by server if they are on a repository return [] if storage_id.blank? || repository_vm? when 'microsoft' return [] if storage_id.blank? else _log.debug("else") return [] end host_server_ids = host ? host.vm_scan_affinity.collect(&:id) : [] _log.debug("host_server_ids.length = #{host_server_ids.length}") storage_server_ids = storages.collect { |s| s.vm_scan_affinity.collect(&:id) }.reject(&:blank?) _log.debug("storage_server_ids.length = #{storage_server_ids.length}") all_storage_server_ids = storage_server_ids.inject(:&) || [] _log.debug("all_storage_server_ids.length = #{all_storage_server_ids.length}") srs = self.class.miq_servers_for_scan _log.debug("srs.length = #{srs.length}") miq_servers = srs.select do |svr| (svr.vm_scan_host_affinity? ? host_server_ids.detect { |id| id == svr.id } : host_server_ids.empty?) && (svr.vm_scan_storage_affinity? ? all_storage_server_ids.detect { |id| id == svr.id } : storage_server_ids.empty?) end _log.debug("miq_servers1.length = #{miq_servers.length}") miq_servers.select! do |svr| result = svr.status == "started" && svr.has_zone?(my_zone) result &&= svr.is_vix_disk? if vendor == 'vmware' result end _log.debug("miq_servers2.length = #{miq_servers.length}") miq_servers end def active_proxy_error_message proxies4job[:message] end # TODO: Vmware specific def repository_vm? host.nil? end # TODO: Vmware specficMethod `template=` has a Cognitive Complexity of 13 (exceeds 11 allowed). Consider refactoring. def template=(val)Do not return a value in `template=`. return val unless val ^ template # Only continue if toggling setting write_attribute(:template, val) self.type = corresponding_model.name if (template? && kind_of?(Vm)) || (!template? && kind_of?(MiqTemplate)) d = template? ? [/\.vmx$/, ".vmtx", 'never'] : [/\.vmtx$/, ".vmx", state == 'never' ? 'unknown' : raw_power_state] self.location = location.sub(d[0], d[1]) unless location.nil? self.raw_power_state = d[2] end # TODO: Vmware specfic def runnable? host_id.present? && current_state != "never" end def self.post_refresh_ems(ems_id, update_start_time) update_start_time = update_start_time.utc ems = ExtManagementSystem.find(ems_id) # Collect the newly added VMs added_vms = ems.vms_and_templates.where("created_on >= ?", update_start_time) # Create queue items to do additional process like apply tags and link events unless added_vms.empty? added_vm_ids = [] added_vms.find_each do |v| v.post_create_actions_queue added_vm_ids << v.id end end post_refresh_ems_folder_updates(ems, update_start_time, added_vms) end Cyclomatic complexity for post_refresh_ems_folder_updates is too high. [14/11] def self.post_refresh_ems_folder_updates(ems, update_start_time, added_vms) # Collect the updated folder relationships to determine which vms need updated path information ems_folders = ems.ems_folders MiqPreloader.preload(ems_folders, :all_relationships) # Find any VMs that were created or moved into a new folder updated_vm_rels = ems_folders.collect do |f| f.relationships.collect do |r| r.children.select do |child_r| child_r.resource_type == "VmOrTemplate" && (child_r.created_at >= update_start_time || child_r.updated_at >= update_start_time) end end end.flatten # Now find any Folders that were renamed or moved into a new parent folder updated_folders = ems_folders.select do |f| f.created_on >= update_start_time || f.updated_on >= update_start_time || # Has the folder itself changed (e.g. renamed)? f.relationships.any? do |r| r.created_at >= update_start_time || r.updated_at >= update_start_time # Has the relationship changed (e.g. this folder moved under another folder)? end end updated_vms = VmOrTemplate.where(:id => updated_vm_rels.collect(&:resource_id)) updated_vms += updated_folders.flat_map(&:all_vms_and_templates) updated_vms = updated_vms.uniq - added_vms updated_vms.each(&:classify_with_parent_folder_path_queue) end private_class_method :post_refresh_ems_folder_updates def post_create_actions_queue MiqQueue.submit_job( :class_name => self.class.name, :instance_id => id, :method_name => 'post_create_actions' ) end def post_create_actions reconnect_events classify_with_parent_folder_path raise_created_event end def raise_created_event raise NotImplementedError, _("raise_created_event must be implemented in a subclass") end # TODO: Vmware specific # Determines the full path from the Storage and location def path # If Storage id is blank return the location stored for the vm after removing the uri data # Otherwise build the path from the storage data and vm location. return location if storage_id.blank? # Return location if it contains a fully-qualified file URI return location if location.starts_with?('file://') # Return location for RHEV-M VMs return rhevm_config_path if vendor.to_s.downcase == 'redhat' if storage.store_type == "NAS" File.join(storage.name, location) elsif storage.storage_type_supported_for_ssa? "[#{storage.name}] #{location}" else _log.warn("VM [#{name}] storage type [#{storage.store_type}] not supported") @path = location end end def rhevm_config_path # /rhev/data-center/<datacenter_id>/mastersd/master/vms/<vm_guid>/<vm_guid>.ovf/ datacenter = parent_datacenter return location if datacenter.blank? File.join('/rhev/data-center', datacenter.uid_ems, 'mastersd/master/vms', uid_ems, location) end def state (power_state || "unknown").downcase end alias_method :current_state, :state # Override raw_power_state= attribute setter in order to impose side effects # of setting previous_state and updating state_changed_on def raw_power_state=(new_state) return unless new_state unless raw_power_state == new_state self.previous_state = raw_power_state self.state_changed_on = Time.now.utc super self.power_state = calculate_power_state endVariable `new_state` used in void context. new_state end def self.calculate_power_state(raw_power_state) (raw_power_state == "never") ? "never" : "unknown" end def archived? ems_id.nil? && storage_id.nil? end alias_method :archived, :archived? virtual_attribute :archived, :boolean, :arel => (lambda do |t| t.grouping(t[:ems_id].eq(nil).and(t[:storage_id].eq(nil))) end) def orphaned? ems_id.nil? && !storage_id.nil? end alias_method :orphaned, :orphaned? virtual_attribute :orphaned, :boolean, :arel => (lambda do |t| t.grouping(t[:ems_id].eq(nil).and(t[:storage_id].not_eq(nil))) end) def active? !archived? && !orphaned? && !retired? && !template? end alias_method :active, :active? # in sql nil != false ==> false virtual_attribute :active, :boolean, :arel => (lambda do |t| t.grouping(t[:ems_id].not_eq(nil) .and(t[:retired].eq(nil).or(t[:retired].eq(t.create_false))) .and(t[:template].eq(nil).or(t[:template].eq(t.create_false)))) end) def disconnected? return self["disconnected"] if has_attribute?("disconnected") !connected_to_ems? end virtual_attribute :disconnected, :boolean, :arel => (lambda do |t| t.grouping(t[:connection_state].not_eq(nil).and(t[:connection_state].not_eq("connected"))) end) alias_method :disconnected, :disconnected? def normalized_state return self["normalized_state"] if has_attribute?("normalized_state") %w[archived orphaned template retired disconnected].each do |s| return s if send(:"#{s}?") end return power_state.downcase unless power_state.nil? "unknown" end virtual_attribute :normalized_state, :string, :arel => (lambda do |t| t.grouping( Arel::Nodes::Case.new .when(arel_table[:archived]).then(Arel.sql("'archived'")) .when(arel_table[:orphaned]).then(Arel.sql("'orphaned'")) .when(t[:template].eq(t.create_true)).then(Arel.sql("'template'")) .when(t[:retired].eq(t.create_true)).then(Arel.sql("'retired'")) .when(arel_table[:disconnected]).then(Arel.sql("'disconnected'")) .else(t.lower( t.coalesce([t[:power_state], Arel.sql("'unknown'")]) )) ) end) def classify_with_parent_folder_path_queue(add = true) MiqQueue.submit_job( :class_name => self.class.name, :instance_id => id, :method_name => 'classify_with_parent_folder_path', :args => [add], :priority => MiqQueue::MIN_PRIORITY ) end def classify_with_parent_folder_path(add = true) [:blue, :yellow].each do |folder_type| path = send(:"parent_#{folder_type}_folder_path") next if path.blank? cat = self.class.folder_category(folder_type) ent = self.class.folder_entry(path, cat) _log.info("#{add ? "C" : "Unc"}lassifying VM: [#{name}] with Category: [#{cat.name} => #{cat.description}], Entry: [#{ent.name} => #{ent.description}]") ent.send(add ? :assign_entry_to : :remove_entry_from, self, false) end end def self.folder_category(folder_type) cat_name = "folder_path_#{folder_type}" cat = Classification.lookup_by_name(cat_name) unless cat cat = Classification.is_category.new( :name => cat_name, :description => "Parent Folder Path (#{folder_type == :blue ? "VMs & Templates" : "Hosts & Clusters"})", :single_value => true, :read_only => true ) cat.save(:validate => false) end cat end def self.folder_entry(ent_desc, cat) ent_name = ent_desc.downcase.tr(" ", "_").split("/").join(":") ent = cat.find_entry_by_name(ent_name) unless ent ent = cat.children.new(:name => ent_name, :description => ent_desc) ent.save(:validate => false) end ent end def event_where_clause(assoc = :ems_events) case assoc.to_sym when :ems_events, :event_streams ["vm_or_template_id = ? OR dest_vm_or_template_id = ? ", id, id] when :policy_events ["target_id = ? and target_class = ? ", id, self.class.base_class.name] end end # Virtual columns for owning resource pool, folder and datacenter def v_owning_cluster o = owning_cluster o ? o.name : "" end def v_owning_resource_pool o = owning_resource_pool o ? o.name : "" end def v_owning_folder o = owning_folder o ? o.name : "" end alias_method :v_owning_folder_path, :owning_folder_path def v_owning_blue_folder o = owning_blue_folder o ? o.name : "" end alias_method :v_owning_blue_folder_path, :owning_blue_folder_path def v_owning_datacenter o = owning_datacenter o ? o.name : "" end def v_is_a_template template?.to_s.capitalize end # technically it is capitalized, but for sorting, not a concern # but we do need nil to become false virtual_attribute :v_is_a_template, :string, :arel => (lambda do |t| t.grouping(t.coalesce([t[:template], t.create_false])) end) def v_datastore_path datastorepath = location || "" storage ? "#{storage.name}/#{datastorepath}" : datastorepath end def event_threshold?(options = {:time_threshold => 30.minutes, :event_types => ["MigrateVM_Task_Complete"], :freq_threshold => 2}) raise _("option :event_types is required") unless options[:event_types] raise _("option :time_threshold is required") unless options[:time_threshold] raise _("option :freq_threshold is required") unless options[:freq_threshold] EmsEvent .where(:event_type => options[:event_types]) .where("vm_or_template_id = :id OR dest_vm_or_template_id = :id", :id => id) .where("timestamp >= ?", options[:time_threshold].to_i.seconds.ago.utc) .count >= options[:freq_threshold].to_i end def reconfigured_hardware_value?(options) attr = options[:hdw_attr] raise _(":hdw_attr required") if attr.nil? operator = options[:operator] || ">" operator = operator.downcase == "increased" ? ">" : operator.downcase == "decreased" ? "<" : operator current_state, prev_state = drift_states.order("timestamp DESC").limit(2) if current_state.nil? || prev_state.nil? _log.info("Unable to evaluate, not enough state data available") return false end current_value = current_state.data_obj.hardware.send(attr).to_i previous_value = prev_state.data_obj.hardware.send(attr).to_i result = current_value.send(operator, previous_value) _log.info("Evaluate: (Current: #{current_value} #{operator} Previous: #{previous_value}) = #{result}") result end def changed_vm_value?(options) attr = options[:attr] || options[:hdw_attr] raise _(":attr required") if attr.nil? operator = options[:operator] data0, data1 = drift_states.order("timestamp DESC").limit(2) if data0.nil? || data1.nil? _log.info("Unable to evaluate, not enough state data available") return false end v0 = data0.data_obj.send(attr) || "" v1 = data1.data_obj.send(attr) || "" if operator.downcase == "changed" result = !(v0 == v1) else raise _("operator '%{operator}' is not supported") % {:operator => operator} end _log.info("Evaluate: !(#{v1} == #{v0}) = #{result}") result end # # Hardware Disks/Memory storage methods # virtual_delegate :allocated_disk_storage, :used_disk_storage, :to => :hardware, :allow_nil => true, :uses => {:hardware => :disks}, :type => :integer virtual_delegate :provisioned_storage, :to => :hardware, :allow_nil => true, :default => 0, :type => :integer virtual_delegate :num_disks, :to => :hardware, :allow_nil => true, :default => 0, :type => :integer, :uses => {:hardware => :disks} virtual_delegate :num_hard_disks, :to => :hardware, :allow_nil => true, :default => 0, :type => :integer, :uses => {:hardware => :hard_disks} def used_storage used_disk_storage.to_i + ram_size_in_bytes end def used_storage_by_state used_disk_storage.to_i + ram_size_in_bytes_by_state end def uncommitted_storage provisioned_storage.to_i - used_storage_by_state.to_i end def thin_provisioned hardware.nil? ? false : hardware.disks.any? { |d| d.disk_type == 'thin' } end def ram_size_by_state state == 'on' ? ram_size : 0 end def ram_size_in_bytes_by_state ram_size_by_state * 1.megabyte end def has_rdm_disk return false if hardware.nil? !hardware.disks.detect(&:rdm_disk?).nil? end def disks_aligned dlist = hardware ? hardware.hard_disks : [] dlist = dlist.reject(&:rdm_disk?) # Skip RDM disks return "Unknown" if dlist.empty? return "True" if dlist.all? { |d| d.partitions_aligned == "True" } return "False" if dlist.any? { |d| d.partitions_aligned == "False" } "Unknown" end def memory_exceeds_current_host_headroom return false if host.nil? (ram_size > host.current_memory_headroom) end def collect_running_processes(_options = {}) OsProcess.add_elements(self, running_processes) operating_system.save unless operating_system.nil? end def ipaddresses hardware.nil? ? [] : hardware.ipaddresses end def hostnames hardware.nil? ? [] : hardware.hostnames end def mac_addresses hardware.nil? ? [] : hardware.mac_addresses end def processes operating_system.nil? ? [] : operating_system.processes end def event_logs operating_system.nil? ? [] : operating_system.event_logs end def direct_service direct_services.first end def service direct_service.try(:root_service) end def has_required_host? !host.nil? end def has_active_ems? return true unless ext_management_system.nil? false end # # Metric methods # PERF_ROLLUP_CHILDREN = [] def perf_rollup_parents(interval_name = nil) [host, service].compact unless interval_name == 'realtime' end # Called from integrate ws to kick off scan for vdi VMs def self.vms_by_ipaddress(ipaddress) ipaddresses = ipaddress.split(',') Network.where("ipaddress in (?)", ipaddresses).each do |network| begin vm = network.hardware.vm yield(vm)Do not suppress exceptions. rescue end end end # This creates the following SQL conditional: # # 1 = (SELECT 1 # FROM hardwares # JOIN networks ON networks.hardware_id = hardwares.id # WHERE hardwares.vm_or_template_id = vms.id # AND (networks.ipaddress LIKE "%IPADDRESS%" # OR networks.ipv6address LIKE "%IPADDRESS%") # LIMIT 1 # ) # # This is simply an existance check, so when one record is found matching the # following conditions: # # - It is a hardware record that is associated with the vm # - It has an ipaddress or ipv6address that matches the search # # It will return the VM record. def self.miq_expression_includes_any_ipaddresses_arel(ipaddress) vms = arel_table networks = Network.arel_table hardwares = Hardware.arel_table match_grouping = networks[:ipaddress].matches("%#{ipaddress}%") .or(networks[:ipv6address].matches("%#{ipaddress}%")) query = hardwares.project(1) .join(networks).on(networks[:hardware_id].eq(hardwares[:id])) .where(hardwares[:vm_or_template_id].eq(vms[:id]).and(match_grouping)) .take(1) Arel.sql("1").eq(query) end def self.scan_by_property(property, value, _options = {}) _log.info("scan_vm_by_property called with property:[#{property}] value:[#{value}]") case property when "ipaddress" vms_by_ipaddress(value) do |vm| if vm.state == "on" _log.info("Initiating VM scan for [#{vm.id}:#{vm.name}]") vm.scan end end else raise _("Unsupported property type [%{property}]") % {:property => property} end end Avoid parameter lists longer than 5 parameters. [6/5] def self.event_by_property(property, value, event_type, event_message, event_time = nil, _options = {}) _log.info("event_vm_by_property called with property:[#{property}] value:[#{value}] type:[#{event_type}] message:[#{event_message}] event_time:[#{event_time}]") event_timestamp = event_time.blank? ? Time.now.utc : event_time.to_time(:utc) case property when "ipaddress" vms_by_ipaddress(value) do |vm| vm.add_ems_event(event_type, event_message, event_timestamp) end when "uid_ems" vm = VmOrTemplate.find_by(:uid_ems => value) unless vm.nil? vm.add_ems_event(event_type, event_message, event_timestamp) end else raise _("Unsupported property type [%{property}]") % {:property => property} end end def add_ems_event(event_type, event_message, event_timestamp) event = { :event_type => event_type, :is_task => false, :source => 'EVM', :message => event_message, :timestamp => event_timestamp, :vm_or_template_id => id, :vm_name => name, :vm_location => path, } event[:ems_id] = ems_id unless ems_id.nil? unless host_id.nil? event[:host_id] = host_id event[:host_name] = host.name end EmsEvent.add(ems_id, event) end def console_supported?(_type) false end # Stop certain charts from showing unless the subclass allows def non_generic_charts_available? false end alias_method :cpu_ready_available?, :non_generic_charts_available? alias_method :cpu_mhz_available?, :non_generic_charts_available? alias_method :cpu_percent_available?, :non_generic_charts_available? alias_method :memory_mb_available?, :non_generic_charts_available? def self.includes_template?(ids) MiqTemplate.where(:id => ids).exists? end supports :destroy # Stop showing Reconfigure VM task unless the subclass allows def reconfigurable? false end def self.reconfigurable?(ids) vms = VmOrTemplate.where(:id => ids) return false if vms.blank? vms.all?(&:reconfigurable?) end PUBLIC_TEMPLATE_CLASSES = %w[ManageIQ::Providers::Openstack::CloudManager::Template].freeze def self.tenant_id_clause(user_or_group) template_tenant_ids = MiqTemplate.accessible_tenant_ids(user_or_group, Rbac.accessible_tenant_ids_strategy(MiqTemplate)) vm_tenant_ids = Vm.accessible_tenant_ids(user_or_group, Rbac.accessible_tenant_ids_strategy(Vm)) return if template_tenant_ids.empty? && vm_tenant_ids.empty? tenant = user_or_group.current_tenant tenant_vms = "vms.template = false AND vms.tenant_id IN (?)" public_templates = "vms.template = true AND vms.publicly_available = true AND vms.type IN (?)" tenant_templates = "vms.template = true AND vms.tenant_id IN (?)" if tenant.source_id private_tenant_templates = "vms.template = true AND vms.tenant_id = (?) AND vms.publicly_available = false" tenant_templates += " AND vms.type NOT IN (?)" ["#{private_tenant_templates} OR #{tenant_vms} OR #{tenant_templates} OR #{public_templates}", tenant.id, vm_tenant_ids, template_tenant_ids, PUBLIC_TEMPLATE_CLASSES, PUBLIC_TEMPLATE_CLASSES] else ["#{tenant_templates} OR #{public_templates} OR #{tenant_vms}", template_tenant_ids, PUBLIC_TEMPLATE_CLASSES, vm_tenant_ids] end end def self.with_ownership includes(:ext_management_system).where(:ext_management_systems => {:tenant_mapping_enabled => [false, nil]}) end def tenant_identity user = evm_owner user = User.super_admin.tap { |u| u.current_group = miq_group } if user.nil? || !user.miq_group_ids.include?(miq_group_id) user end supports(:console) { N_("Console not supported") unless console_supported?('spice') || console_supported?('vnc') } def child_resources children end def parent_resource parent end def self.display_name(number = 1) n_('VM or Template', 'VMs or Templates', number) end private Useless method definition detected. def power_state=(new_power_state) super end def calculate_power_state self.class.calculate_power_state(raw_power_state) end # deprecated, use unsupported_reason(:action) instead def check_feature_support(_message_prefix) reason = unsupported_reason(:action) [!reason, reason] end def create_notification(type, options) Notification.create!( :type => type, :subject => self, :options => options ) end def command_queue_options(queue_options) { :class_name => self.class.name, :instance_id => id, :role => "ems_operations", :queue_name => queue_name_for_ems_operations, :zone => my_zone, }.merge(queue_options) endend