def test_blocking_wait_already_there(self): dict = BlockingDict() dict[1] = 1 dict[1] = 2 value = dict.wait_until(1, lambda v: v == 2, timeout=2) assert_that(value, is_(2)) assert_that(dict.num_keys_with_waiters, is_(0)) assert_that(dict.num_waiters, is_(0))
def __init__( self, host="localhost", user=None, pwd=None, wait_timeout=10, min_interval=1, auto_sync=True, ticket=None, stats_interval=600, errback=None, ): self._logger = logging.getLogger(__name__) self.host = host self.current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._host_stats = {} self._ds_name_properties = {} self._vm_cache_lock = threading.RLock() self._host_cache_lock = threading.Lock() self._task_cache = BlockingDict() self._task_counter_lock = threading.Lock() self._task_counter = 0 self.filter = None self.min_interval = min_interval self.sync_thread = None self.wait_timeout = wait_timeout self.stats_interval = stats_interval self.username = None self.password = None self.auto_sync = auto_sync self.errback = errback self.update_listeners = set() if ticket: self._si = self.connect_ticket(host, ticket) else: if not user or not pwd: (self.username, self.password) = VimClient.acquire_credentials() else: (self.username, self.password) = (user, pwd) self._si = self.connect_userpwd(host, self.username, self.password) self._content = self._si.RetrieveContent() if auto_sync: # Initialize host stat counters. self.initialize_host_counters() # Initialize vm cache self.update_cache() # Start syncing vm cache periodically self._start_syncing_cache()
def __init__(self): self._logger = logging.getLogger(__name__) self._filter = None self._current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._ds_cache = {} self._lock = threading.RLock() self._task_cache = BlockingDict() self._memory_usage = 0
def __init__(self, host="localhost", user=None, pwd=None, wait_timeout=10, min_interval=1, auto_sync=True, ticket=None, stats_interval=600, errback=None): self._logger = logging.getLogger(__name__) self.host = host self.current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._host_stats = {} self._ds_name_properties = {} self._vm_cache_lock = threading.RLock() self._host_cache_lock = threading.Lock() self._task_cache = BlockingDict() self._task_counter_lock = threading.Lock() self._task_counter = 0 self.filter = None self.min_interval = min_interval self.sync_thread = None self.wait_timeout = wait_timeout self.stats_interval = stats_interval self.username = None self.password = None self.auto_sync = auto_sync self.errback = errback self.update_listeners = set() if ticket: self._si = self.connect_ticket(host, ticket) else: if not user or not pwd: (self.username, self.password) = \ VimClient.acquire_credentials() else: (self.username, self.password) = (user, pwd) self._si = self.connect_userpwd(host, self.username, self.password) self._content = self._si.RetrieveContent() if auto_sync: # Initialize host stat counters. self.initialize_host_counters() # Initialize vm cache self.update_cache() # Start syncing vm cache periodically self._start_syncing_cache()
def __init__(self): self._logger = logging.getLogger(__name__) self._filter = None self._current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._ds_cache = {} self._lock = threading.RLock() self._task_cache = BlockingDict() self._memory_usage = 0
def test_wait_on_none(self): dict = BlockingDict() def updater(): time.sleep(1) dict[1] = 2 dict[1] = 3 thread = threading.Thread(target=updater) thread.start() value = dict.wait_until(1, lambda v: v == 3, timeout=2) thread.join() assert_that(value, is_(3)) assert_that(dict.num_keys_with_waiters, is_(0)) assert_that(dict.num_waiters, is_(0))
def test_blocking_wait_on_none(self): dict = BlockingDict() dict[1] = 1 def updater(): dict[1] = 1 del dict[1] thread = threading.Thread(target=updater) thread.start() value = dict.wait_until(1, lambda v: v is None, timeout=2) thread.join() assert_that(value, is_(None)) assert_that(dict.num_keys_with_waiters, is_(0)) assert_that(dict.num_waiters, is_(0))
def test_blocking_wait_del_items(self): dict = BlockingDict() dict[1] = 1 def updater(): dict[1] = 2 del dict[1] dict[1] = 3 thread = threading.Thread(target=updater) thread.start() value = dict.wait_until(1, lambda v: v == 3, timeout=2) thread.join() assert_that(value, is_(3)) assert_that(dict.num_keys_with_waiters, is_(0)) assert_that(dict.num_waiters, is_(0))
class VimClient(object): """Wrapper class around VIM API calls using Service Instance connection""" ALLOC_LARGE_PAGES = "Mem.AllocGuestLargePage" def __init__(self, host="localhost", user=None, pwd=None, wait_timeout=10, min_interval=1, auto_sync=True, ticket=None, stats_interval=600, errback=None): self._logger = logging.getLogger(__name__) self.host = host self.current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._host_stats = {} self._ds_name_properties = {} self._vm_cache_lock = threading.RLock() self._host_cache_lock = threading.Lock() self._task_cache = BlockingDict() self._task_counter_lock = threading.Lock() self._task_counter = 0 self.filter = None self.min_interval = min_interval self.sync_thread = None self.wait_timeout = wait_timeout self.stats_interval = stats_interval self.username = None self.password = None self.auto_sync = auto_sync self.errback = errback self.update_listeners = set() if ticket: self._si = self.connect_ticket(host, ticket) else: if not user or not pwd: (self.username, self.password) = \ VimClient.acquire_credentials() else: (self.username, self.password) = (user, pwd) self._si = self.connect_userpwd(host, self.username, self.password) self._content = self._si.RetrieveContent() if auto_sync: # Initialize host stat counters. self.initialize_host_counters() # Initialize vm cache self.update_cache() # Start syncing vm cache periodically self._start_syncing_cache() @lock_with("_vm_cache_lock") def add_update_listener(self, listener): # Notify the listener immediately since there might have already been # some updates. listener.networks_updated() listener.virtual_machines_updated() listener.datastores_updated() self.update_listeners.add(listener) @lock_with("_vm_cache_lock") def remove_update_listener(self, listener): self.update_listeners.discard(listener) def initialize_host_counters(self): """ Initializes the the list of host perf counters that we are interested in. The perf counters are specified in the form of strings - <group>.<metric>. The ids for the counters are not well defined values and the only way to get them is to read all the counters and get the ids for the ones we are interested in. """ # TODO(badhri): Should this be a config option? host_counters = { "mem.consumed": None, "rescpu.actav1": None } self.id_to_counter_map = {} # Initialize the perf counter ids. for c in self.perf_manager.perfCounter: counter = "%s.%s" % (c.groupInfo.key, c.nameInfo.key) if counter in host_counters: host_counters[counter] = c self.id_to_counter_map[c.key] = counter self.host_counters = [ vim.PerfMetricId( counterId=_counter.key, instance="" ) for _counter in host_counters.values() if _counter ] # Samples are 20 seconds apart self.stats_sample_interval = 20 def get_perf_manager_stats(self, time_delta=3600): """ Returns the host statistics by querying the perf manager on the host for the configured performance counters. :param time_delta [int]: in seconds """ # Get an time_delta worth of stats and summarize. These are # stats are sampled by the performance manager every 20 # seconds. Hostd keeps 180 samples at the rate of 1 sample # per 20 seconds, which results in samples that span an hour. end_time = datetime.now() start_time = end_time - timedelta(seconds=time_delta) query_spec = [ vim.PerfQuerySpec( entity=self.host_system, intervalId=self.stats_sample_interval, format='csv', metricId=self.host_counters, startTime=start_time, endTime=end_time )] results = {} stats = self.perf_manager.QueryPerf(query_spec) if not stats: return results # Average the received stats. For now this implementation is only # for consumed memory. If other stats are considered in future, the # implementation might be per stat dependent. for stat in stats: values = stat.value for value in values: counter_id = value.id.counterId counter_values = [int(i) for i in value.value.split(',')] average = sum(counter_values) / len(counter_values) results[self.id_to_counter_map[counter_id]] = average return results def connect_ticket(self, host, ticket): if ticket: try: stub = SoapStubAdapter(host, HOSTD_PORT, VIM_NAMESPACE) si = vim.ServiceInstance("ServiceInstance", stub) si.RetrieveContent().sessionManager.CloneSession(ticket) return si except httplib.HTTPException as http_exception: self._logger.info("Failed to login hostd with ticket: %s" % http_exception) raise AcquireCredentialsException(http_exception) def connect_userpwd(self, host, user, pwd): try: si = connect.Connect(host=host, user=user, pwd=pwd, version=VIM_VERSION) return si except vim.fault.HostConnectFault as connection_exception: self._logger.info( "Failed to connect to hostd: %s" % connection_exception) raise HostdConnectionFailure(connection_exception) def disconnect(self, wait=False): """ Disconnect vim client :param wait: If wait is true, it waits until the sync thread exit. """ self._logger.info("vimclient disconnect") self._stop_syncing_cache(wait=wait) try: connect.Disconnect(self._si) except: self._logger.warning("Failed to disconnect vim_client: %s" % sys.exc_info()[1]) @property @hostd_error_handler def perf_manager(self): return self._content.perfManager @property @hostd_error_handler def virtual_disk_manager(self): return self._content.virtualDiskManager @property @hostd_error_handler def task_manager(self): return self._content.taskManager @property @hostd_error_handler def vmotion_manager(self): return host.GetVmotionManager(self._si) @property @hostd_error_handler def vnic_manager(self): return host.GetHostVirtualNicManager(self._si) @property @hostd_error_handler def session_manager(self): return self._content.sessionManager @property @hostd_error_handler def property_collector(self): return self._content.propertyCollector @property @hostd_error_handler def vmotion_ip(self): return host.GetVMotionIP(self._si) @property @hostd_error_handler def host_uuid(self): return host.GetHostUuid(self._si) @property @hostd_error_handler def nfc_service(self): return vim.NfcService('ha-nfc-service', self._si._stub) @property @hostd_error_handler def root_resource_pool(self): """Get the root resource pool for this host. :rtype: vim.ResourcePool """ return host.GetRootResourcePool(self._si) @property @cached() @hostd_error_handler def vm_folder(self): """Get the default vm folder for this host. :rtype: vim.Folder """ return invt.GetVmFolder(si=self._si) @property @hostd_error_handler def host_system(self): return host.GetHostSystem(self._si) @property @hostd_error_handler def search_index(self): """ Reference to the inventory search index. :rtype: vim.SearchIndex """ return self._content.searchIndex @property @hostd_error_handler def physical_nics(self): return self.host_system.config.network.pnic @property @hostd_error_handler def memory_usage_mb(self): return self.host_system.summary.quickStats.overallMemoryUsage @property @hostd_error_handler def total_vmusable_memory_mb(self): return self.host_system.summary.hardware.memorySize >> 20 @property @hostd_error_handler def mac_addresses(self): pnics = self.physical_nics return [pnic.mac for pnic in pnics if pnic.mac != ""] @property @hostd_error_handler def num_physical_cpus(self): """ Returns the number of pCPUs on the host. 1 pCPU is one hyper thread, if HT is enabled. :return: number of pCPUs """ return self.host_system.summary.hardware.numCpuThreads @property @hostd_error_handler def first_vmk_ip_address(self): vnics = self.host_system.config.network.vnic first_vmk = next(vnic for vnic in vnics if vnic.device == "vmk0") if first_vmk: return first_vmk.spec.ip.ipAddress else: return None @property @hostd_error_handler def about(self): """ :rtype: vim.AboutInfo """ return self._content.about @hostd_error_handler def get_nfc_ticket_by_ds_name(self, datastore): """ :param datastore: str, datastore name :rtype: vim.HostServiceTicket """ ds = self.get_datastore(datastore) if not ds: raise DatastoreNotFound('Datastore %s not found' % datastore) return self.nfc_service.FileManagement(ds) @hostd_error_handler def acquire_clone_ticket(self): """ acquire a clone ticket of current session, that can be used to login as current user. :return: str, ticket """ return self.session_manager.AcquireCloneTicket() @hostd_error_handler def acquire_cgi_ticket(self, url, op): """ acquire a cgi ticket to perform a HTTP operation on a URL :return: str, ticket """ http_method = vim.SessionManager.HttpServiceRequestSpec.Method _op_map = { HttpOp.GET: http_method.httpGet, HttpOp.PUT: http_method.httpPut, HttpOp.POST: http_method.httpPost } httpsvc_spec = vim.SessionManager.HttpServiceRequestSpec() httpsvc_spec.url = url httpsvc_spec.method = _op_map[op] ticket = self.session_manager.AcquireGenericServiceTicket(httpsvc_spec) return ticket.id @hostd_error_handler def inventory_path(self, *path): """ Convert a tuple of strings to a path for use with `find_by_inventory_path`. :param path: Inventory path :rtype: str """ dc = (HA_DATACENTER_ID,) return "/".join(p.replace("/", "%2f") for p in dc + path if p) @hostd_error_handler def find_by_inventory_path(self, *path): """ Finds a managed entity based on its location in the inventory. :param path: Inventory path :type path: tuple :rtype: vim.ManagedEntity """ p = self.inventory_path(*path) return self.search_index.FindByInventoryPath(p) def get_vm_path_info(self, vm): """Get the datastore and relative vmx paths for a vm. Args: vm: The vim vm reference to get the path info for. Return: The datastore and relative vmx paths for a vm. """ # This is "[datastore1 (3)] dummy_vm/dummy_vm.vmx" vm_path = vm.config.files.vmPathName # This is "datastore1 (3)" datastore_name = vm_path[vm_path.index("[") + 1:vm_path.rindex("]")] # This is "dummy_vm/dummy_vm.vmx" vm_rel_path = vm_path[vm_path.rindex("]") + 1:].strip() datastore_path = self.datastore_name_to_path(datastore_name) return datastore_path, vm_rel_path @hostd_error_handler def datastore_name_to_path(self, name): """Get the absolute datastore path for a datastore name. A name is something like 'datastore1' or 'storage'. Args: name: The name of the datastore. Return: A string with the host specific identifier of the datastore. """ host_cfg = self.host_system.config host_mounts = host_cfg.fileSystemVolume.mountInfo for mount in host_mounts: if mount.volume.name == name: return mount.mountInfo.path return None @hostd_error_handler def get_vm_power_state(self, vm): """Get the power state for a vm. Args: vm: The vim vm reference to get the power state for. Return: The power state: 'poweredOff', 'poweredOn' or 'suspended' """ return vm.runtime.powerState @hostd_error_handler def get_vm(self, vm_id): """Get the vm reference on a host. Args: vm_id: The name of the vm. Returns: A vim vm reference. """ vm = self.find_by_inventory_path(VM_FOLDER_NAME, vm_id) if not vm: raise VmNotFoundException("VM '%s' not found on host." % vm_id) return vm @cached() @hostd_error_handler def get_datastore_folder(self): """Get the datastore folder for this host. :rtype: vim.Folder """ return self.find_by_inventory_path(DATASTORE_FOLDER_NAME) @hostd_error_handler def get_datastore(self, name=None): """Get a datastore network for this host. :param name: Optional datastore name :type name: str :rtype: vim.Datastore """ if name is None: return self.get_all_datastores()[0] return self.find_by_inventory_path(DATASTORE_FOLDER_NAME, name) @hostd_error_handler def get_all_datastores(self): """Get all datastores for this host. :rtype: list of vim.Datastore """ return self.get_datastore_folder().childEntity @hostd_error_handler def get_vms(self): """ Get VirtualMachine from hostd. Use get_vms_in_cache to have a better performance unless you want Vim Object. :return: list of vim.VirtualMachine """ filter_spec = self.vm_filter_spec() objects = self.property_collector.RetrieveContents([filter_spec]) return [object.obj for object in objects] @lock_with("_vm_cache_lock") def get_vms_in_cache(self): """ Get information of all VMs from cache. :return: list of VmCache """ return [copy.copy(vm) for vm in self._vm_cache.values() if self._validate_vm(vm)] @lock_with("_vm_cache_lock") def get_vm_in_cache(self, vm_id): """ Get information of a VM from cache. The vm state is not guaranteed to be up-to-date. Also only name and power_state is guaranteed to be not None. :return: VmCache for the vm that is found :raise VmNotFoundException when vm is not found """ if vm_id not in self._vm_name_to_ref: raise VmNotFoundException("VM '%s' not found on host." % vm_id) ref = self._vm_name_to_ref[vm_id] vm = self._vm_cache[ref] if self._validate_vm(vm): return copy.copy(vm) else: raise VmNotFoundException("VM '%s' not found on host." % vm_id) @lock_with("_vm_cache_lock") def get_vm_obj_in_cache(self, vm_id): """ Get vim vm object given ID of the vm. :return: vim.VirtualMachine object :raise VmNotFoundException when vm is not found """ if vm_id not in self._vm_name_to_ref: raise VmNotFoundException("VM '%s' not found on host." % vm_id) moid = self._vm_name_to_ref[vm_id].split(":")[-1][:-1] return vim.VirtualMachine(moid, self._si._stub) @hostd_error_handler def update_cache(self, timeout=10): """Polling on VM updates on host. This call will block caller until update of VM is available or timeout. :param timeout: timeout in seconds """ if not self.filter: self.filter = self.property_collector.CreateFilter( self.filter_spec(), partialUpdates=False) wait_options = vmodl.query.PropertyCollector.WaitOptions() wait_options.maxWaitSeconds = timeout update = self.property_collector.WaitForUpdatesEx( self.current_version, wait_options) self._update_cache(update) if update: self.current_version = update.version return update @cached() @hostd_error_handler def get_network_folder(self): """Get the network folder for this host. :rtype: vim.Folder """ return self.find_by_inventory_path(NETWORK_FOLDER_NAME) @hostd_error_handler def get_network(self, name=None): """Get a VM network for this host. :param name: Optional network name :type name: str :rtype: vim.Network """ if not name: return self.get_network_folder().childEntity[0] return self.find_by_inventory_path(NETWORK_FOLDER_NAME, name) @hostd_error_handler def get_networks(self): return [network.name for network in self.get_network_folder().childEntity] @hostd_error_handler def get_network_configs(self): """Get NetConfig list :return: vim.host.VirtualNicManager.NetConfig[] """ return self.vnic_manager.info.netConfig @staticmethod def _verify_task_done(task_cache): if not task_cache: return False state = task_cache.state if state == TaskState.error or state == TaskState.success: return True else: return False @hostd_error_handler @log_duration_with(log_level="debug") def wait_for_task(self, vim_task, timeout=DEFAULT_TASK_TIMEOUT): if not self.auto_sync: raise Exception("wait_for_task only works when auto_sync=True") self._task_counter_add() self._logger.debug("wait_for_task: {0} Number of current tasks: {1}". format(str(vim_task), self._task_counter_read())) try: task_cache = self._task_cache.wait_until( str(vim_task), VimClient._verify_task_done, timeout=timeout) finally: self._task_counter_sub() self._logger.debug("task(%s) finished with: %s" % (str(vim_task), str(task_cache))) if task_cache.state == TaskState.error: if not task_cache.error: task_cache.error = "No message" raise task_cache.error else: return task_cache @hostd_error_handler @log_duration_with(log_level="debug") def spin_wait_for_task(self, vim_task): """Use pysdk's WaitForTask, which basically polling task status in a loop. """ self._task_counter_add() self._logger.debug( "spin_wait_for_task: {0} Number of current tasks: {1}". format(str(vim_task), self._task_counter_read())) try: task.WaitForTask(vim_task, si=self._si) finally: self._task_counter_sub() @log_duration_with(log_level="debug") def wait_for_vm_create(self, vm_id, timeout=10): """Wait for vm to be created in cache :raise TimeoutError when timeout """ self._vm_name_to_ref.wait_until(vm_id, lambda x: x is not None, timeout) @log_duration_with(log_level="debug") def wait_for_vm_delete(self, vm_id, timeout=10): """Wait for vm to be deleted from cache :raise TimeoutError when timeout """ self._vm_name_to_ref.wait_until(vm_id, None, timeout) @staticmethod def acquire_credentials(): credentials = Credentials() return credentials.username, credentials.password @staticmethod def get_hostd_ssl_thumbprint(): digest = VimClient._hostd_certbytes_digest() thumbprint = ":".join(digest[i:i+2] for i in xrange(0, len(digest), 2)) return thumbprint @staticmethod def _hostd_certbytes_digest(): cert = ssl.get_server_certificate(("localhost", HOSTD_PORT)) certbytes = ssl.PEM_cert_to_DER_cert(cert) m = hashlib.sha1() m.update(certbytes) return m.hexdigest().upper() def datastore_filter_spec(self): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec( name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False ) property_spec = PC.PropertySpec( type=vim.Datastore, pathSet=["name"] ) object_spec = PC.ObjectSpec( obj=self.get_datastore_folder(), selectSet=[traversal_spec] ) return PC.FilterSpec( propSet=[property_spec], objectSet=[object_spec]) def network_filter_spec(self): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec( name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False ) property_spec = PC.PropertySpec( type=vim.Network, pathSet=["name"] ) object_spec = PC.ObjectSpec( obj=self.get_network_folder(), selectSet=[traversal_spec] ) return PC.FilterSpec( propSet=[property_spec], objectSet=[object_spec]) def vm_filter_spec(self): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec( name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False ) property_spec = PC.PropertySpec( type=vim.VirtualMachine, pathSet=["name", "runtime.powerState", "layout.disk", "config"] ) object_spec = PC.ObjectSpec( obj=self.vm_folder, selectSet=[traversal_spec] ) return PC.FilterSpec( propSet=[property_spec], objectSet=[object_spec]) def task_filter_spec(self): PC = vmodl.query.PropertyCollector task_property_spec = PC.PropertySpec( type=vim.Task, pathSet=["info.error", "info.state"] ) task_traversal_spec = PC.TraversalSpec( name="taskSpec", type=vim.TaskManager, path="recentTask", skip=False ) task_object_spec = PC.ObjectSpec( obj=self.task_manager, selectSet=[task_traversal_spec] ) return PC.FilterSpec( propSet=[task_property_spec], objectSet=[task_object_spec] ) def filter_spec(self): PC = vmodl.query.PropertyCollector ds_spec = self.datastore_filter_spec() nw_spec = self.network_filter_spec() task_spec = self.task_filter_spec() vm_spec = self.vm_filter_spec() propSet = ds_spec.propSet + nw_spec.propSet + task_spec.propSet + \ vm_spec.propSet objectSet = ds_spec.objectSet + nw_spec.objectSet + \ task_spec.objectSet + vm_spec.objectSet return PC.FilterSpec(propSet=propSet, objectSet=objectSet) def _apply_ds_update(self, obj_update): ds_key = str(obj_update.obj) updated = False if obj_update.kind == "enter" or obj_update.kind == "modify": for change in obj_update.changeSet: if change.name == "name": ds_name = change.val self._logger.debug("cache update: %s ds name %s" % (obj_update.kind, ds_name)) if (ds_key not in self._ds_name_properties or self._ds_name_properties[ds_key] != ds_name): updated = True self._ds_name_properties[ds_key] = ds_name elif obj_update.kind == "leave": self._logger.debug("cache update: remove ds ref %s" % ds_key) if ds_key in self._ds_name_properties: del self._ds_name_properties[ds_key] updated = True return updated @lock_with("_vm_cache_lock") def _update_cache(self, update): if not update or not update.filterSet: return ds_updated = False nw_updated = False vm_updated = False for filter in update.filterSet: for object in filter.objectSet: # Update Vm cache if isinstance(object.obj, vim.VirtualMachine): if object.kind == "enter": # Possible to have 2 enters for one object vm_updated = True self._add_or_modify_vm_cache(object) elif object.kind == "leave": assert str(object.obj) in self._vm_cache, \ "%s not in cache for kind leave" % object.obj vm_updated = True self._remove_vm_cache(object) elif object.kind == "modify": assert str(object.obj) in self._vm_cache, \ "%s not in cache for kind modify" % object.obj vm_updated = True self._add_or_modify_vm_cache(object) # Update task cache elif isinstance(object.obj, vim.Task): if object.kind == "enter": self._update_task_cache(object) elif object.kind == "leave": self._remove_task_cache(object) elif object.kind == "modify": self._update_task_cache(object) elif isinstance(object.obj, vim.Network): self._logger.debug("Network changed: %s" % object) nw_updated = True elif isinstance(object.obj, vim.Datastore): self._logger.debug("Datastore update: %s" % object) updated = self._apply_ds_update(object) ds_updated = ds_updated or updated # Notify listeners. for listener in self.update_listeners: if ds_updated: self._logger.debug("datastores updated for listener: %s" % (listener.__class__.__name__)) listener.datastores_updated() if nw_updated: self._logger.debug("networks updated for listener: %s" % (listener.__class__.__name__)) listener.networks_updated() if vm_updated: self._logger.debug( "virtual machines updated for listener: %s" % (listener.__class__.__name__)) listener.virtual_machines_updated() def _add_or_modify_vm_cache(self, object): # Type of object.obj is vim.VirtualMachine. str(object.obj) is moref # id, something like 'vim.VirtualMachine:1227'. moref id is the unique # representation of all objects in esx. if str(object.obj) not in self._vm_cache: vm = VmCache() else: vm = self._vm_cache[str(object.obj)] for change in object.changeSet: # We are not interested in ops other than assign. # Other operations e.g. add/remove/indirectRemove, only appears # when a property is added/removed. However the changes we # watched are all static. if change.op != "assign": continue # None value is not updated if change.val is None: continue if change.name == "name": vm.name = change.val self._logger.debug("cache update: add vm name %s" % vm.name) self._vm_name_to_ref[change.val] = str(object.obj) elif change.name == "runtime.powerState": vm.power_state = PowerState._NAMES_TO_VALUES[change.val] elif change.name == "config": vm.memory_mb = change.val.hardware.memoryMB vm.num_cpu = change.val.hardware.numCPU # files is an optional field, which could be None. if change.val.files: vm.path = change.val.files.vmPathName for e in change.val.extraConfig: if e.key == "photon_controller.vminfo.tenant": vm.tenant_id = e.value elif e.key == "photon_controller.vminfo.project": vm.project_id = e.value elif change.name == "layout.disk": disks = [] for disk in change.val: if disk.diskFile: for disk_file in disk.diskFile: disks.append(disk_file) vm.disks = disks self._logger.debug("cache update: update vm [%s] => %s" % (str( object.obj), vm)) if str(object.obj) not in self._vm_cache: self._vm_cache[str(object.obj)] = vm def _remove_vm_cache(self, object): ref_id = str(object.obj) assert ref_id in self._vm_cache, "%s not in cache" % ref_id vm_cache = self._vm_cache[ref_id] self._logger.debug("cache update: delete vm [%s] => %s" % (ref_id, vm_cache)) del self._vm_cache[ref_id] if vm_cache.name in self._vm_name_to_ref: # Only delete map in _vm_name_to_ref when it points to the right # ref_id. If it points to another ref_id, it means a new VM is # created with the same name, which cannot be deleted. if self._vm_name_to_ref[vm_cache.name] == ref_id: del self._vm_name_to_ref[vm_cache.name] def _update_task_cache(self, object): task_cache = TaskCache() for change in object.changeSet: if change.op != "assign": continue if change.val is None: continue if change.name == "info.error": task_cache.error = change.val elif change.name == "info.state": task_cache.state = TaskState._NAMES_TO_VALUES[change.val] self._logger.debug("task cache update: update task [%s] => %s" % (str(object.obj), task_cache)) self._task_cache[str(object.obj)] = task_cache def _remove_task_cache(self, object): assert str(object.obj) in self._task_cache, "%s not in cache" % str( object.obj) self._logger.debug( "task cache update: remove task [%s] => %s" % (str(object.obj), self._task_cache[str(object.obj)])) del self._task_cache[str(object.obj)] def _validate_vm(self, vm): try: vm.validate() return True except: return False def _start_syncing_cache(self): self._logger.info("Start vim client sync vm cache thread") self.sync_thread = SyncVmCacheThread(self, self.wait_timeout, self.min_interval, self.stats_interval, errback=self.errback) self.sync_thread.start() def _stop_syncing_cache(self, wait=False): if self.sync_thread: self.sync_thread.stop() if wait: self.sync_thread.join() @lock_with("_task_counter_lock") def _task_counter_add(self): self._task_counter += 1 @lock_with("_task_counter_lock") def _task_counter_sub(self): self._task_counter -= 1 @lock_with("_task_counter_lock") def _task_counter_read(self): return self._task_counter @hostd_error_handler def update_hosts_stats(self): stats = self.get_perf_manager_stats() self._update_host_cache(stats) @lock_with("_host_cache_lock") def _update_host_cache(self, stats): self._host_stats = copy.copy(stats) @lock_with("_host_cache_lock") def get_host_stats(self): return copy.copy(self._host_stats) def set_large_page_support(self, disable=False): """Disables large page support on the ESX hypervisor This is done when the host memory is overcommitted. """ optionManager = self.host_system.configManager.advancedOption option = vim.OptionValue() option.key = self.ALLOC_LARGE_PAGES if disable: option.value = 0L self._logger.warning("Disabling large page support") else: option.value = 1L self._logger.warning("Enabling large page support") optionManager.UpdateOptions([option])
class VimCache: """ Initialization """ def __init__(self): self._logger = logging.getLogger(__name__) self._filter = None self._current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._ds_cache = {} self._lock = threading.RLock() self._task_cache = BlockingDict() self._memory_usage = 0 """ Create filter """ def _datastore_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec(name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False) property_spec = PC.PropertySpec(type=vim.Datastore, pathSet=[ "name", "summary.capacity", "summary.freeSpace", "summary.type" ]) from host.hypervisor.esx.vim_client import DATASTORE_FOLDER_NAME object_spec = PC.ObjectSpec( obj=vim_client._find_by_inventory_path(DATASTORE_FOLDER_NAME), selectSet=[traversal_spec]) return PC.FilterSpec(propSet=[property_spec], objectSet=[object_spec]) def _vm_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec(name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False) property_spec = PC.PropertySpec( type=vim.VirtualMachine, pathSet=["name", "runtime.powerState", "layout.disk", "config"]) object_spec = PC.ObjectSpec(obj=vim_client._vm_folder(), selectSet=[traversal_spec]) return PC.FilterSpec(propSet=[property_spec], objectSet=[object_spec]) def _task_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector task_property_spec = PC.PropertySpec( type=vim.Task, pathSet=["info.error", "info.state"]) task_traversal_spec = PC.TraversalSpec(name="taskSpec", type=vim.TaskManager, path="recentTask", skip=False) task_object_spec = PC.ObjectSpec(obj=vim_client._content.taskManager, selectSet=[task_traversal_spec]) return PC.FilterSpec(propSet=[task_property_spec], objectSet=[task_object_spec]) def _host_system_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector host_property_spec = PC.PropertySpec( type=vim.HostSystem, pathSet=["summary.quickStats.overallMemoryUsage"]) host_traversal_spec = PC.TraversalSpec(name="hostSpec", type=vim.ComputeResource, path="host", skip=False) host_object_spec = PC.ObjectSpec(obj=vim_client.host_system(), selectSet=[host_traversal_spec]) return PC.FilterSpec(propSet=[host_property_spec], objectSet=[host_object_spec]) def _build_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector ds_spec = self._datastore_filter_spec(vim_client) task_spec = self._task_filter_spec(vim_client) vm_spec = self._vm_filter_spec(vim_client) host_spec = self._host_system_filter_spec(vim_client) propSet = ds_spec.propSet + task_spec.propSet + vm_spec.propSet + host_spec.propSet objectSet = ds_spec.objectSet + task_spec.objectSet + vm_spec.objectSet + host_spec.objectSet return PC.FilterSpec(propSet=propSet, objectSet=objectSet) """ Poll updates """ def poll_updates(self, vim_client, timeout=10): """Polling on VM updates on host. This call will block caller until update of VM is available or timeout. :param timeout: timeout in seconds """ if not self._filter: filter_spec = self._build_filter_spec(vim_client) self._filter = vim_client._property_collector.CreateFilter( filter_spec, partialUpdates=False) wait_options = vmodl.query.PropertyCollector.WaitOptions() wait_options.maxWaitSeconds = timeout update = vim_client._property_collector.WaitForUpdatesEx( self._current_version, wait_options) self._update_cache(vim_client, update) if update: self._current_version = update.version return update @lock_with("_lock") def _update_cache(self, vim_client, update): if not update or not update.filterSet: return ds_updated = False for filter in update.filterSet: for object in filter.objectSet: # Update Vm cache if isinstance(object.obj, vim.VirtualMachine): if object.kind == "enter" or object.kind == "modify": self._update_vm_cache(object) elif object.kind == "leave": self._remove_vm_cache(object) # Update task cache elif isinstance(object.obj, vim.Task): if object.kind == "enter" or object.kind == "modify": self._update_task_cache(object) elif object.kind == "leave": self._remove_task_cache(object) elif isinstance(object.obj, vim.Datastore): if object.kind == "enter" or object.kind == "modify": if self._update_ds_cache(object): ds_updated = True elif object.kind == "leave": self._remove_ds_cache(object) ds_updated = True elif isinstance(object.obj, vim.HostSystem): if object.kind == "enter" or object.kind == "modify": self._update_host_system_cache(object) # Notify listeners. if ds_updated: for listener in vim_client.update_listeners: self._logger.debug("datastores updated for listener: %s" % listener.__class__.__name__) listener.datastores_updated() def _update_vm_cache(self, object): # Type of object.obj is vim.VirtualMachine. str(object.obj) is moref # id, something like 'vim.VirtualMachine:1227'. moref id is the unique # representation of all objects in esx. if str(object.obj) not in self._vm_cache: vm = VmCache() else: vm = self._vm_cache[str(object.obj)] for change in object.changeSet: # We are not interested in ops other than assign. # Other operations e.g. add/remove/indirectRemove, only appears # when a property is added/removed. However the changes we # watched are all static. if change.op != "assign": continue # None value is not updated if change.val is None: continue if change.name == "name": vm.name = change.val self._logger.debug("cache update: add vm name %s" % vm.name) self._vm_name_to_ref[change.val] = str(object.obj) elif change.name == "runtime.powerState": if change.val == "poweredOff": vm.power_state = VmPowerState.STOPPED elif change.val == "poweredOn": vm.power_state = VmPowerState.STARTED elif change.val == "suspended": vm.power_state = VmPowerState.SUSPENDED elif change.name == "config": vm.memory_mb = change.val.hardware.memoryMB vm.num_cpu = change.val.hardware.numCPU vm.location_id = change.val.locationId # files is an optional field, which could be None. if change.val.files: vm.path = change.val.files.vmPathName elif change.name == "layout.disk": disks = [] for disk in change.val: if disk.diskFile: for disk_file in disk.diskFile: disks.append(disk_file) vm.disks = disks self._logger.debug("cache update: update vm [%s] => %s" % (str(object.obj), vm)) if str(object.obj) not in self._vm_cache: self._vm_cache[str(object.obj)] = vm def _remove_vm_cache(self, object): ref_id = str(object.obj) assert ref_id in self._vm_cache, "%s not in cache" % ref_id vm_cache = self._vm_cache[ref_id] self._logger.debug("cache update: delete vm [%s] => %s" % (ref_id, vm_cache)) del self._vm_cache[ref_id] if vm_cache.name in self._vm_name_to_ref: # Only delete map in _vm_name_to_ref when it points to the right # ref_id. If it points to another ref_id, it means a new VM is # created with the same name, which cannot be deleted. if self._vm_name_to_ref[vm_cache.name] == ref_id: del self._vm_name_to_ref[vm_cache.name] def _update_ds_cache(self, object): updated = False key = str(object.obj) if key not in self._ds_cache: ds = VimDatastore() ds.id = key.split(':')[1] else: ds = self._ds_cache[key] for change in object.changeSet: if change.op != "assign": continue if change.val is None: continue if change.name == "name": if key not in self._ds_cache or self._ds_cache[ key].name != change.val: self._logger.debug("cache update: %s ds name %s" % (object.kind, change.val)) ds.name = change.val # Datastore added or renamed, need to notify datastore_manager updated = True elif change.name == "summary.capacity": ds.capacity = change.val elif change.name == "summary.freeSpace": ds.free = change.val elif change.name == "summary.type": ds.type = change.val self._logger.debug("cache update: update ds [%s] => %s" % (key, vars(ds))) if key not in self._ds_cache: self._ds_cache[key] = ds return updated def _remove_ds_cache(self, object): key = str(object.obj) self._logger.debug("cache update: remove ds %s" % key) if key in self._ds_cache: del self._ds_cache[key] def _update_task_cache(self, object): task_cache = TaskCache() for change in object.changeSet: if change.op != "assign": continue if change.val is None: continue if change.name == "info.error": task_cache.error = change.val elif change.name == "info.state": task_cache.state = TaskState._NAMES_TO_VALUES[change.val] self._logger.debug("task cache update: update task [%s] => %s" % (str(object.obj), task_cache)) self._task_cache[str(object.obj)] = task_cache def _remove_task_cache(self, object): assert str(object.obj) in self._task_cache, "%s not in cache" % str( object.obj) self._logger.debug( "task cache update: remove task [%s] => %s" % (str(object.obj), self._task_cache[str(object.obj)])) del self._task_cache[str(object.obj)] def _update_host_system_cache(self, object): for change in object.changeSet: if change.name == "summary.quickStats.overallMemoryUsage": self._memory_usage = change.val self._logger.debug("host_system cache update: memoryUsage=%d" % self._memory_usage) """ Accessors """ @lock_with("_lock") def get_vm_ids_in_cache(self): """ Get information of all VMs from cache. :return: list of VmId strings """ return [ vm.name for vm in self._vm_cache.values() if self._validate_vm(vm) ] @lock_with("_lock") def get_vms_in_cache(self): """ Get information of all VMs from cache. :return: list of VmCache """ return [ copy.copy(vm) for vm in self._vm_cache.values() if self._validate_vm(vm) ] @lock_with("_lock") def get_vm_in_cache(self, vm_id): """ Get information of a VM from cache. The vm state is not guaranteed to be up-to-date. Also only name and power_state is guaranteed to be not None. :return: VmCache for the vm that is found :raise VmNotFoundException when vm is not found """ if vm_id not in self._vm_name_to_ref: raise VmNotFoundException("VM '%s' not found on host." % vm_id) ref = self._vm_name_to_ref[vm_id] vm = self._vm_cache[ref] if self._validate_vm(vm): return copy.copy(vm) else: raise VmNotFoundException("VM '%s' not found on host." % vm_id) @lock_with("_lock") def get_ds_in_cache(self, ds_name): for ds in self._ds_cache: if ds.name == ds_name: return ds raise DatastoreNotFoundException("Datastore '%s' not found on host." % ds_name) @lock_with("_lock") def get_memory_usage(self): return self._memory_usage def wait_for_task(self, vim_task, timeout): return self._task_cache.wait_until(str(vim_task), self._verify_task_done, timeout=timeout) def wait_for_vm_create(self, vm_id): self._vm_name_to_ref.wait_until(vm_id, lambda x: x is not None) def wait_for_vm_delete(self, vm_id): self._vm_name_to_ref.wait_until(vm_id, None) """ Helpers """ @staticmethod def _validate_vm(vm): try: vm.validate() return True except: return False @staticmethod def _verify_task_done(task_cache): if not task_cache: return False state = task_cache.state if state == TaskState.error or state == TaskState.success: return True else: return False
class VimCache: """ Initialization """ def __init__(self): self._logger = logging.getLogger(__name__) self._filter = None self._current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._ds_cache = {} self._lock = threading.RLock() self._task_cache = BlockingDict() self._memory_usage = 0 """ Create filter """ def _datastore_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec(name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False) property_spec = PC.PropertySpec(type=vim.Datastore, pathSet=["name", "summary.capacity", "summary.freeSpace", "summary.type"]) from host.hypervisor.esx.vim_client import DATASTORE_FOLDER_NAME object_spec = PC.ObjectSpec(obj=vim_client._find_by_inventory_path(DATASTORE_FOLDER_NAME), selectSet=[traversal_spec]) return PC.FilterSpec(propSet=[property_spec], objectSet=[object_spec]) def _vm_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec(name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False) property_spec = PC.PropertySpec(type=vim.VirtualMachine, pathSet=["name", "runtime.powerState", "layout.disk", "config"]) object_spec = PC.ObjectSpec(obj=vim_client._vm_folder(), selectSet=[traversal_spec]) return PC.FilterSpec(propSet=[property_spec], objectSet=[object_spec]) def _task_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector task_property_spec = PC.PropertySpec(type=vim.Task, pathSet=["info.error", "info.state"]) task_traversal_spec = PC.TraversalSpec(name="taskSpec", type=vim.TaskManager, path="recentTask", skip=False) task_object_spec = PC.ObjectSpec(obj=vim_client._content.taskManager, selectSet=[task_traversal_spec]) return PC.FilterSpec(propSet=[task_property_spec], objectSet=[task_object_spec]) def _host_system_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector host_property_spec = PC.PropertySpec(type=vim.HostSystem, pathSet=["summary.quickStats.overallMemoryUsage"]) host_traversal_spec = PC.TraversalSpec(name="hostSpec", type=vim.ComputeResource, path="host", skip=False) host_object_spec = PC.ObjectSpec(obj=vim_client.host_system, selectSet=[host_traversal_spec]) return PC.FilterSpec(propSet=[host_property_spec], objectSet=[host_object_spec]) def _build_filter_spec(self, vim_client): PC = vmodl.query.PropertyCollector ds_spec = self._datastore_filter_spec(vim_client) task_spec = self._task_filter_spec(vim_client) vm_spec = self._vm_filter_spec(vim_client) host_spec = self._host_system_filter_spec(vim_client) propSet = ds_spec.propSet + task_spec.propSet + vm_spec.propSet + host_spec.propSet objectSet = ds_spec.objectSet + task_spec.objectSet + vm_spec.objectSet + host_spec.objectSet return PC.FilterSpec(propSet=propSet, objectSet=objectSet) """ Poll updates """ def poll_updates(self, vim_client, timeout=10): """Polling on VM updates on host. This call will block caller until update of VM is available or timeout. :param timeout: timeout in seconds """ if not self._filter: filter_spec = self._build_filter_spec(vim_client) self._filter = vim_client._property_collector.CreateFilter(filter_spec, partialUpdates=False) wait_options = vmodl.query.PropertyCollector.WaitOptions() wait_options.maxWaitSeconds = timeout update = vim_client._property_collector.WaitForUpdatesEx(self._current_version, wait_options) self._update_cache(vim_client, update) if update: self._current_version = update.version return update @lock_with("_lock") def _update_cache(self, vim_client, update): if not update or not update.filterSet: return ds_updated = False for filter in update.filterSet: for object in filter.objectSet: # Update Vm cache if isinstance(object.obj, vim.VirtualMachine): if object.kind == "enter" or object.kind == "modify": self._update_vm_cache(object) elif object.kind == "leave": self._remove_vm_cache(object) # Update task cache elif isinstance(object.obj, vim.Task): if object.kind == "enter" or object.kind == "modify": self._update_task_cache(object) elif object.kind == "leave": self._remove_task_cache(object) elif isinstance(object.obj, vim.Datastore): if object.kind == "enter" or object.kind == "modify": if self._update_ds_cache(object): ds_updated = True elif object.kind == "leave": self._remove_ds_cache(object) ds_updated = True elif isinstance(object.obj, vim.HostSystem): if object.kind == "enter" or object.kind == "modify": self._update_host_system_cache(object) # Notify listeners. if ds_updated: for listener in vim_client.update_listeners: self._logger.debug("datastores updated for listener: %s" % listener.__class__.__name__) listener.datastores_updated() def _update_vm_cache(self, object): # Type of object.obj is vim.VirtualMachine. str(object.obj) is moref # id, something like 'vim.VirtualMachine:1227'. moref id is the unique # representation of all objects in esx. if str(object.obj) not in self._vm_cache: vm = VmCache() else: vm = self._vm_cache[str(object.obj)] for change in object.changeSet: # We are not interested in ops other than assign. # Other operations e.g. add/remove/indirectRemove, only appears # when a property is added/removed. However the changes we # watched are all static. if change.op != "assign": continue # None value is not updated if change.val is None: continue if change.name == "name": vm.name = change.val self._logger.debug("cache update: add vm name %s" % vm.name) self._vm_name_to_ref[change.val] = str(object.obj) elif change.name == "runtime.powerState": if change.val == "poweredOff": vm.power_state = VmPowerState.STOPPED elif change.val == "poweredOn": vm.power_state = VmPowerState.STARTED elif change.val == "suspended": vm.power_state = VmPowerState.SUSPENDED elif change.name == "config": vm.memory_mb = change.val.hardware.memoryMB vm.num_cpu = change.val.hardware.numCPU vm.location_id = change.val.locationId # files is an optional field, which could be None. if change.val.files: vm.path = change.val.files.vmPathName elif change.name == "layout.disk": disks = [] for disk in change.val: if disk.diskFile: for disk_file in disk.diskFile: disks.append(disk_file) vm.disks = disks self._logger.debug("cache update: update vm [%s] => %s" % (str(object.obj), vm)) if str(object.obj) not in self._vm_cache: self._vm_cache[str(object.obj)] = vm def _remove_vm_cache(self, object): ref_id = str(object.obj) assert ref_id in self._vm_cache, "%s not in cache" % ref_id vm_cache = self._vm_cache[ref_id] self._logger.debug("cache update: delete vm [%s] => %s" % (ref_id, vm_cache)) del self._vm_cache[ref_id] if vm_cache.name in self._vm_name_to_ref: # Only delete map in _vm_name_to_ref when it points to the right # ref_id. If it points to another ref_id, it means a new VM is # created with the same name, which cannot be deleted. if self._vm_name_to_ref[vm_cache.name] == ref_id: del self._vm_name_to_ref[vm_cache.name] def _update_ds_cache(self, object): updated = False key = str(object.obj) if key not in self._ds_cache: ds = VimDatastore() ds.id = key.split(':')[1] else: ds = self._ds_cache[key] for change in object.changeSet: if change.op != "assign": continue if change.val is None: continue if change.name == "name": if key not in self._ds_cache or self._ds_cache[key].name != change.val: self._logger.debug("cache update: %s ds name %s" % (object.kind, change.val)) ds.name = change.val # Datastore added or renamed, need to notify datastore_manager updated = True elif change.name == "summary.capacity": ds.capacity = change.val elif change.name == "summary.freeSpace": ds.free = change.val elif change.name == "summary.type": ds.type = change.val self._logger.debug("cache update: update ds [%s] => %s" % (key, vars(ds))) if key not in self._ds_cache: self._ds_cache[key] = ds return updated def _remove_ds_cache(self, object): key = str(object.obj) self._logger.debug("cache update: remove ds %s" % key) if key in self._ds_cache: del self._ds_cache[key] def _update_task_cache(self, object): task_cache = TaskCache() for change in object.changeSet: if change.op != "assign": continue if change.val is None: continue if change.name == "info.error": task_cache.error = change.val elif change.name == "info.state": task_cache.state = TaskState._NAMES_TO_VALUES[change.val] self._logger.debug("task cache update: update task [%s] => %s" % (str(object.obj), task_cache)) self._task_cache[str(object.obj)] = task_cache def _remove_task_cache(self, object): assert str(object.obj) in self._task_cache, "%s not in cache" % str(object.obj) self._logger.debug("task cache update: remove task [%s] => %s" % (str(object.obj), self._task_cache[str(object.obj)])) del self._task_cache[str(object.obj)] def _update_host_system_cache(self, object): for change in object.changeSet: if change.name == "summary.quickStats.overallMemoryUsage": self._memory_usage = change.val self._logger.debug("host_system cache update: memoryUsage=%d" % self._memory_usage) """ Accessors """ @lock_with("_lock") def get_vm_ids_in_cache(self): """ Get information of all VMs from cache. :return: list of VmId strings """ return [vm.name for vm in self._vm_cache.values() if self._validate_vm(vm)] @lock_with("_lock") def get_vms_in_cache(self): """ Get information of all VMs from cache. :return: list of VmCache """ return [copy.copy(vm) for vm in self._vm_cache.values() if self._validate_vm(vm)] @lock_with("_lock") def get_vm_in_cache(self, vm_id): """ Get information of a VM from cache. The vm state is not guaranteed to be up-to-date. Also only name and power_state is guaranteed to be not None. :return: VmCache for the vm that is found :raise VmNotFoundException when vm is not found """ if vm_id not in self._vm_name_to_ref: raise VmNotFoundException("VM '%s' not found on host." % vm_id) ref = self._vm_name_to_ref[vm_id] vm = self._vm_cache[ref] if self._validate_vm(vm): return copy.copy(vm) else: raise VmNotFoundException("VM '%s' not found on host." % vm_id) @lock_with("_lock") def get_ds_in_cache(self, ds_name): for ds in self._ds_cache: if ds.name == ds_name: return ds raise DatastoreNotFoundException("Datastore '%s' not found on host." % ds_name) @lock_with("_lock") def get_memory_usage(self): return self._memory_usage def wait_for_task(self, vim_task, timeout): return self._task_cache.wait_until(str(vim_task), self._verify_task_done, timeout=timeout) def wait_for_vm_create(self, vm_id): self._vm_name_to_ref.wait_until(vm_id, lambda x: x is not None) def wait_for_vm_delete(self, vm_id): self._vm_name_to_ref.wait_until(vm_id, None) """ Helpers """ @staticmethod def _validate_vm(vm): try: vm.validate() return True except: return False @staticmethod def _verify_task_done(task_cache): if not task_cache: return False state = task_cache.state if state == TaskState.error or state == TaskState.success: return True else: return False
class VimClient(object): """Wrapper class around VIM API calls using Service Instance connection""" ALLOC_LARGE_PAGES = "Mem.AllocGuestLargePage" def __init__(self, host="localhost", user=None, pwd=None, wait_timeout=10, min_interval=1, auto_sync=True, ticket=None, stats_interval=600, errback=None): self._logger = logging.getLogger(__name__) self.host = host self.current_version = None self._vm_cache = {} self._vm_name_to_ref = BlockingDict() self._host_stats = {} self._ds_name_properties = {} self._vm_cache_lock = threading.RLock() self._host_cache_lock = threading.Lock() self._task_cache = BlockingDict() self._task_counter_lock = threading.Lock() self._task_counter = 0 self.filter = None self.min_interval = min_interval self.sync_thread = None self.wait_timeout = wait_timeout self.stats_interval = stats_interval self.username = None self.password = None self.auto_sync = auto_sync self.errback = errback self.update_listeners = set() if ticket: self._si = self.connect_ticket(host, ticket) else: if not user or not pwd: (self.username, self.password) = \ VimClient.acquire_credentials() else: (self.username, self.password) = (user, pwd) self._si = self.connect_userpwd(host, self.username, self.password) self._content = self._si.RetrieveContent() if auto_sync: # Initialize host stat counters. self.initialize_host_counters() # Initialize vm cache self.update_cache() # Start syncing vm cache periodically self._start_syncing_cache() @lock_with("_vm_cache_lock") def add_update_listener(self, listener): # Notify the listener immediately since there might have already been # some updates. listener.networks_updated() listener.virtual_machines_updated() listener.datastores_updated() self.update_listeners.add(listener) @lock_with("_vm_cache_lock") def remove_update_listener(self, listener): self.update_listeners.discard(listener) def initialize_host_counters(self): """ Initializes the the list of host perf counters that we are interested in. The perf counters are specified in the form of strings - <group>.<metric>. The ids for the counters are not well defined values and the only way to get them is to read all the counters and get the ids for the ones we are interested in. """ # TODO(badhri): Should this be a config option? host_counters = { "mem.consumed": None, "rescpu.actav1": None } self.id_to_counter_map = {} # Initialize the perf counter ids. for c in self.perf_manager.perfCounter: counter = "%s.%s" % (c.groupInfo.key, c.nameInfo.key) if counter in host_counters: host_counters[counter] = c self.id_to_counter_map[c.key] = counter self.host_counters = [ vim.PerfMetricId( counterId=_counter.key, instance="" ) for _counter in host_counters.values() if _counter ] # Samples are 20 seconds apart self.stats_sample_interval = 20 def get_perf_manager_stats(self, time_delta=3600): """ Returns the host statistics by querying the perf manager on the host for the configured performance counters. :param time_delta [int]: in seconds """ # Get an time_delta worth of stats and summarize. These are # stats are sampled by the performance manager every 20 # seconds. Hostd keeps 180 samples at the rate of 1 sample # per 20 seconds, which results in samples that span an hour. end_time = datetime.now() start_time = end_time - timedelta(seconds=time_delta) query_spec = [ vim.PerfQuerySpec( entity=self.host_system, intervalId=self.stats_sample_interval, format='csv', metricId=self.host_counters, startTime=start_time, endTime=end_time )] results = {} stats = self.perf_manager.QueryPerf(query_spec) if not stats: return results # Average the received stats. For now this implementation is only # for consumed memory. If other stats are considered in future, the # implementation might be per stat dependent. for stat in stats: values = stat.value for value in values: counter_id = value.id.counterId counter_values = [int(i) for i in value.value.split(',')] average = sum(counter_values) / len(counter_values) results[self.id_to_counter_map[counter_id]] = average return results def connect_ticket(self, host, ticket): if ticket: try: stub = SoapStubAdapter(host, HOSTD_PORT, VIM_NAMESPACE) si = vim.ServiceInstance("ServiceInstance", stub) si.RetrieveContent().sessionManager.CloneSession(ticket) return si except httplib.HTTPException as http_exception: self._logger.info("Failed to login hostd with ticket: %s" % http_exception) raise AcquireCredentialsException(http_exception) def connect_userpwd(self, host, user, pwd): try: si = connect.Connect(host=host, user=user, pwd=pwd, version=VIM_VERSION) return si except vim.fault.HostConnectFault as connection_exception: self._logger.info( "Failed to connect to hostd: %s" % connection_exception) raise HostdConnectionFailure(connection_exception) def disconnect(self, wait=False): """ Disconnect vim client :param wait: If wait is true, it waits until the sync thread exit. """ self._logger.info("vimclient disconnect") self._stop_syncing_cache(wait=wait) try: connect.Disconnect(self._si) except: self._logger.warning("Failed to disconnect vim_client: %s" % sys.exc_info()[1]) @property @hostd_error_handler def perf_manager(self): return self._content.perfManager @property @hostd_error_handler def virtual_disk_manager(self): return self._content.virtualDiskManager @property @hostd_error_handler def task_manager(self): return self._content.taskManager @property @hostd_error_handler def vmotion_manager(self): return host.GetVmotionManager(self._si) @property @hostd_error_handler def vnic_manager(self): return host.GetHostVirtualNicManager(self._si) @property @hostd_error_handler def session_manager(self): return self._content.sessionManager @property @hostd_error_handler def property_collector(self): return self._content.propertyCollector @property @hostd_error_handler def vmotion_ip(self): return host.GetVMotionIP(self._si) @property @hostd_error_handler def host_uuid(self): return host.GetHostUuid(self._si) @property @hostd_error_handler def nfc_service(self): return vim.NfcService('ha-nfc-service', self._si._stub) @property @hostd_error_handler def root_resource_pool(self): """Get the root resource pool for this host. :rtype: vim.ResourcePool """ return host.GetRootResourcePool(self._si) @property @hostd_error_handler def vm_folder(self): """Get the default vm folder for this host. :rtype: vim.Folder """ return invt.GetVmFolder(si=self._si) @property @hostd_error_handler def host_system(self): return host.GetHostSystem(self._si) @property @hostd_error_handler def search_index(self): """ Reference to the inventory search index. :rtype: vim.SearchIndex """ return self._content.searchIndex @property @hostd_error_handler def physical_nics(self): return self.host_system.config.network.pnic @property @hostd_error_handler def memory_usage_mb(self): return self.host_system.summary.quickStats.overallMemoryUsage @property @hostd_error_handler def total_vmusable_memory_mb(self): return self.host_system.summary.hardware.memorySize >> 20 @property @hostd_error_handler def mac_addresses(self): pnics = self.physical_nics return [pnic.mac for pnic in pnics if pnic.mac != ""] @property @hostd_error_handler def num_physical_cpus(self): """ Returns the number of pCPUs on the host. 1 pCPU is one hyper thread, if HT is enabled. :return: number of pCPUs """ return self.host_system.summary.hardware.numCpuThreads @property @hostd_error_handler def first_vmk_ip_address(self): vnics = self.host_system.config.network.vnic first_vmk = next(vnic for vnic in vnics if vnic.device == "vmk0") if first_vmk: return first_vmk.spec.ip.ipAddress else: return None @property @hostd_error_handler def about(self): """ :rtype: vim.AboutInfo """ return self._content.about @hostd_error_handler def get_nfc_ticket_by_ds_name(self, datastore): """ :param datastore: str, datastore name :rtype: vim.HostServiceTicket """ ds = self.get_datastore(datastore) if not ds: raise DatastoreNotFound('Datastore %s not found' % datastore) return self.nfc_service.FileManagement(ds) @hostd_error_handler def acquire_clone_ticket(self): """ acquire a clone ticket of current session, that can be used to login as current user. :return: str, ticket """ return self.session_manager.AcquireCloneTicket() @hostd_error_handler def acquire_cgi_ticket(self, url, op): """ acquire a cgi ticket to perform a HTTP operation on a URL :return: str, ticket """ http_method = vim.SessionManager.HttpServiceRequestSpec.Method _op_map = { HttpOp.GET: http_method.httpGet, HttpOp.PUT: http_method.httpPut, HttpOp.POST: http_method.httpPost } httpsvc_spec = vim.SessionManager.HttpServiceRequestSpec() httpsvc_spec.url = url httpsvc_spec.method = _op_map[op] ticket = self.session_manager.AcquireGenericServiceTicket(httpsvc_spec) return ticket.id @hostd_error_handler def inventory_path(self, *path): """ Convert a tuple of strings to a path for use with `find_by_inventory_path`. :param path: Inventory path :rtype: str """ dc = (HA_DATACENTER_ID,) return "/".join(p.replace("/", "%2f") for p in dc + path if p) @hostd_error_handler def find_by_inventory_path(self, *path): """ Finds a managed entity based on its location in the inventory. :param path: Inventory path :type path: tuple :rtype: vim.ManagedEntity """ p = self.inventory_path(*path) return self.search_index.FindByInventoryPath(p) def get_vm_path_info(self, vm): """Get the datastore and relative vmx paths for a vm. Args: vm: The vim vm reference to get the path info for. Return: The datastore and relative vmx paths for a vm. """ # This is "[datastore1 (3)] dummy_vm/dummy_vm.vmx" vm_path = vm.config.files.vmPathName # This is "datastore1 (3)" datastore_name = vm_path[vm_path.index("[") + 1:vm_path.rindex("]")] # This is "dummy_vm/dummy_vm.vmx" vm_rel_path = vm_path[vm_path.rindex("]") + 1:].strip() datastore_path = self.datastore_name_to_path(datastore_name) return datastore_path, vm_rel_path @hostd_error_handler def datastore_name_to_path(self, name): """Get the absolute datastore path for a datastore name. A name is something like 'datastore1' or 'storage'. Args: name: The name of the datastore. Return: A string with the host specific identifier of the datastore. """ host_cfg = self.host_system.config host_mounts = host_cfg.fileSystemVolume.mountInfo for mount in host_mounts: if mount.volume.name == name: return mount.mountInfo.path return None @hostd_error_handler def get_vm_power_state(self, vm): """Get the power state for a vm. Args: vm: The vim vm reference to get the power state for. Return: The power state: 'poweredOff', 'poweredOn' or 'suspended' """ return vm.runtime.powerState @hostd_error_handler def get_vm(self, vm_id): """Get the vm reference on a host. Args: vm_id: The name of the vm. Returns: A vim vm reference. """ vm = self.find_by_inventory_path(VM_FOLDER_NAME, vm_id) if not vm: raise VmNotFoundException("VM '%s' not found on host." % vm_id) return vm @hostd_error_handler def get_datastore_folder(self): """Get the datastore folder for this host. :rtype: vim.Folder """ return self.find_by_inventory_path(DATASTORE_FOLDER_NAME) @hostd_error_handler def get_datastore(self, name=None): """Get a datastore network for this host. :param name: Optional datastore name :type name: str :rtype: vim.Datastore """ if name is None: return self.get_all_datastores()[0] return self.find_by_inventory_path(DATASTORE_FOLDER_NAME, name) @hostd_error_handler def get_all_datastores(self): """Get all datastores for this host. :rtype: list of vim.Datastore """ return self.get_datastore_folder().childEntity @hostd_error_handler def get_vms(self): """ Get VirtualMachine from hostd. Use get_vms_in_cache to have a better performance unless you want Vim Object. :return: list of vim.VirtualMachine """ filter_spec = self.vm_filter_spec() objects = self.property_collector.RetrieveContents([filter_spec]) return [object.obj for object in objects] @lock_with("_vm_cache_lock") def get_vms_in_cache(self): """ Get information of all VMs from cache. :return: list of VmCache """ return [copy.copy(vm) for vm in self._vm_cache.values() if self._validate_vm(vm)] @lock_with("_vm_cache_lock") def get_vm_in_cache(self, vm_id): """ Get information of a VM from cache. The vm state is not guaranteed to be up-to-date. Also only name and power_state is guaranteed to be not None. :return: VmCache for the vm that is found :raise VmNotFoundException when vm is not found """ if vm_id not in self._vm_name_to_ref: raise VmNotFoundException("VM '%s' not found on host." % vm_id) ref = self._vm_name_to_ref[vm_id] vm = self._vm_cache[ref] if self._validate_vm(vm): return copy.copy(vm) else: raise VmNotFoundException("VM '%s' not found on host." % vm_id) @lock_with("_vm_cache_lock") def get_vm_obj_in_cache(self, vm_id): """ Get vim vm object given ID of the vm. :return: vim.VirtualMachine object :raise VmNotFoundException when vm is not found """ if vm_id not in self._vm_name_to_ref: raise VmNotFoundException("VM '%s' not found on host." % vm_id) moid = self._vm_name_to_ref[vm_id].split(":")[-1][:-1] return vim.VirtualMachine(moid, self._si._stub) @hostd_error_handler def update_cache(self, timeout=10): """Polling on VM updates on host. This call will block caller until update of VM is available or timeout. :param timeout: timeout in seconds """ if not self.filter: self.filter = self.property_collector.CreateFilter( self.filter_spec(), partialUpdates=False) wait_options = vmodl.query.PropertyCollector.WaitOptions() wait_options.maxWaitSeconds = timeout update = self.property_collector.WaitForUpdatesEx( self.current_version, wait_options) self._update_cache(update) if update: self.current_version = update.version return update @hostd_error_handler def get_network_folder(self): """Get the network folder for this host. :rtype: vim.Folder """ return self.find_by_inventory_path(NETWORK_FOLDER_NAME) @hostd_error_handler def get_network(self, name=None): """Get a VM network for this host. :param name: Optional network name :type name: str :rtype: vim.Network """ if not name: return self.get_network_folder().childEntity[0] return self.find_by_inventory_path(NETWORK_FOLDER_NAME, name) @hostd_error_handler def get_networks(self): return [network.name for network in self.get_network_folder().childEntity] @hostd_error_handler def get_network_configs(self): """Get NetConfig list :return: vim.host.VirtualNicManager.NetConfig[] """ return self.vnic_manager.info.netConfig @staticmethod def _verify_task_done(task_cache): if not task_cache: return False state = task_cache.state if state == TaskState.error or state == TaskState.success: return True else: return False @hostd_error_handler @log_duration_with(log_level="debug") def wait_for_task(self, vim_task, timeout=DEFAULT_TASK_TIMEOUT): if not self.auto_sync: raise Exception("wait_for_task only works when auto_sync=True") self._task_counter_add() self._logger.debug("wait_for_task: {0} Number of current tasks: {1}". format(str(vim_task), self._task_counter_read())) try: task_cache = self._task_cache.wait_until( str(vim_task), VimClient._verify_task_done, timeout=timeout) finally: self._task_counter_sub() self._logger.debug("task(%s) finished with: %s" % (str(vim_task), str(task_cache))) if task_cache.state == TaskState.error: if not task_cache.error: task_cache.error = "No message" raise task_cache.error else: return task_cache @hostd_error_handler @log_duration_with(log_level="debug") def spin_wait_for_task(self, vim_task): """Use pysdk's WaitForTask, which basically polling task status in a loop. """ self._task_counter_add() self._logger.debug( "spin_wait_for_task: {0} Number of current tasks: {1}". format(str(vim_task), self._task_counter_read())) try: task.WaitForTask(vim_task, si=self._si) finally: self._task_counter_sub() @log_duration_with(log_level="debug") def wait_for_vm_create(self, vm_id, timeout=10): """Wait for vm to be created in cache :raise TimeoutError when timeout """ self._vm_name_to_ref.wait_until(vm_id, lambda x: x is not None, timeout) @log_duration_with(log_level="debug") def wait_for_vm_delete(self, vm_id, timeout=10): """Wait for vm to be deleted from cache :raise TimeoutError when timeout """ self._vm_name_to_ref.wait_until(vm_id, None, timeout) @staticmethod def acquire_credentials(): credentials = Credentials() return credentials.username, credentials.password @staticmethod def get_hostd_ssl_thumbprint(): digest = VimClient._hostd_certbytes_digest() thumbprint = ":".join(digest[i:i+2] for i in xrange(0, len(digest), 2)) return thumbprint @staticmethod def _hostd_certbytes_digest(): cert = ssl.get_server_certificate(("localhost", HOSTD_PORT)) certbytes = ssl.PEM_cert_to_DER_cert(cert) m = hashlib.sha1() m.update(certbytes) return m.hexdigest().upper() def datastore_filter_spec(self): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec( name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False ) property_spec = PC.PropertySpec( type=vim.Datastore, pathSet=["name"] ) object_spec = PC.ObjectSpec( obj=self.get_datastore_folder(), selectSet=[traversal_spec] ) return PC.FilterSpec( propSet=[property_spec], objectSet=[object_spec]) def network_filter_spec(self): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec( name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False ) property_spec = PC.PropertySpec( type=vim.Network, pathSet=["name"] ) object_spec = PC.ObjectSpec( obj=self.get_network_folder(), selectSet=[traversal_spec] ) return PC.FilterSpec( propSet=[property_spec], objectSet=[object_spec]) def vm_filter_spec(self): PC = vmodl.query.PropertyCollector traversal_spec = PC.TraversalSpec( name="folderTraversalSpec", type=vim.Folder, path="childEntity", skip=False ) property_spec = PC.PropertySpec( type=vim.VirtualMachine, pathSet=["name", "runtime.powerState", "layout.disk", "config"] ) object_spec = PC.ObjectSpec( obj=self.vm_folder, selectSet=[traversal_spec] ) return PC.FilterSpec( propSet=[property_spec], objectSet=[object_spec]) def task_filter_spec(self): PC = vmodl.query.PropertyCollector task_property_spec = PC.PropertySpec( type=vim.Task, pathSet=["info.error", "info.state"] ) task_traversal_spec = PC.TraversalSpec( name="taskSpec", type=vim.TaskManager, path="recentTask", skip=False ) task_object_spec = PC.ObjectSpec( obj=self.task_manager, selectSet=[task_traversal_spec] ) return PC.FilterSpec( propSet=[task_property_spec], objectSet=[task_object_spec] ) def filter_spec(self): PC = vmodl.query.PropertyCollector ds_spec = self.datastore_filter_spec() nw_spec = self.network_filter_spec() task_spec = self.task_filter_spec() vm_spec = self.vm_filter_spec() propSet = ds_spec.propSet + nw_spec.propSet + task_spec.propSet + \ vm_spec.propSet objectSet = ds_spec.objectSet + nw_spec.objectSet + \ task_spec.objectSet + vm_spec.objectSet return PC.FilterSpec(propSet=propSet, objectSet=objectSet) def _apply_ds_update(self, obj_update): ds_key = str(obj_update.obj) updated = False if obj_update.kind == "enter" or obj_update.kind == "modify": for change in obj_update.changeSet: if change.name == "name": ds_name = change.val self._logger.debug("cache update: %s ds name %s" % (obj_update.kind, ds_name)) if (ds_key not in self._ds_name_properties or self._ds_name_properties[ds_key] != ds_name): updated = True self._ds_name_properties[ds_key] = ds_name elif obj_update.kind == "leave": self._logger.debug("cache update: remove ds ref %s" % ds_key) if ds_key in self._ds_name_properties: del self._ds_name_properties[ds_key] updated = True return updated @lock_with("_vm_cache_lock") def _update_cache(self, update): if not update or not update.filterSet: return ds_updated = False nw_updated = False vm_updated = False for filter in update.filterSet: for object in filter.objectSet: # Update Vm cache if isinstance(object.obj, vim.VirtualMachine): if object.kind == "enter": # Possible to have 2 enters for one object vm_updated = True self._add_or_modify_vm_cache(object) elif object.kind == "leave": assert str(object.obj) in self._vm_cache, \ "%s not in cache for kind leave" % object.obj vm_updated = True self._remove_vm_cache(object) elif object.kind == "modify": assert str(object.obj) in self._vm_cache, \ "%s not in cache for kind modify" % object.obj vm_updated = True self._add_or_modify_vm_cache(object) # Update task cache elif isinstance(object.obj, vim.Task): if object.kind == "enter": self._update_task_cache(object) elif object.kind == "leave": self._remove_task_cache(object) elif object.kind == "modify": self._update_task_cache(object) elif isinstance(object.obj, vim.Network): self._logger.debug("Network changed: %s" % object) nw_updated = True elif isinstance(object.obj, vim.Datastore): self._logger.debug("Datastore update: %s" % object) updated = self._apply_ds_update(object) ds_updated = ds_updated or updated # Notify listeners. for listener in self.update_listeners: if ds_updated: self._logger.debug("datastores updated for listener: %s" % (listener.__class__.__name__)) listener.datastores_updated() if nw_updated: self._logger.debug("networks updated for listener: %s" % (listener.__class__.__name__)) listener.networks_updated() if vm_updated: self._logger.debug( "virtual machines updated for listener: %s" % (listener.__class__.__name__)) listener.virtual_machines_updated() def _add_or_modify_vm_cache(self, object): # Type of object.obj is vim.VirtualMachine. str(object.obj) is moref # id, something like 'vim.VirtualMachine:1227'. moref id is the unique # representation of all objects in esx. if str(object.obj) not in self._vm_cache: vm = VmCache() else: vm = self._vm_cache[str(object.obj)] for change in object.changeSet: # We are not interested in ops other than assign. # Other operations e.g. add/remove/indirectRemove, only appears # when a property is added/removed. However the changes we # watched are all static. if change.op != "assign": continue # None value is not updated if change.val is None: continue if change.name == "name": vm.name = change.val self._logger.debug("cache update: add vm name %s" % vm.name) self._vm_name_to_ref[change.val] = str(object.obj) elif change.name == "runtime.powerState": vm.power_state = PowerState._NAMES_TO_VALUES[change.val] elif change.name == "config": vm.memory_mb = change.val.hardware.memoryMB vm.num_cpu = change.val.hardware.numCPU # files is an optional field, which could be None. if change.val.files: vm.path = change.val.files.vmPathName elif change.name == "layout.disk": disks = [] for disk in change.val: if disk.diskFile: for disk_file in disk.diskFile: disks.append(disk_file) vm.disks = disks self._logger.debug("cache update: update vm [%s] => %s" % (str( object.obj), vm)) if str(object.obj) not in self._vm_cache: self._vm_cache[str(object.obj)] = vm def _remove_vm_cache(self, object): ref_id = str(object.obj) assert ref_id in self._vm_cache, "%s not in cache" % ref_id vm_cache = self._vm_cache[ref_id] self._logger.debug("cache update: delete vm [%s] => %s" % (ref_id, vm_cache)) del self._vm_cache[ref_id] if vm_cache.name in self._vm_name_to_ref: # Only delete map in _vm_name_to_ref when it points to the right # ref_id. If it points to another ref_id, it means a new VM is # created with the same name, which cannot be deleted. if self._vm_name_to_ref[vm_cache.name] == ref_id: del self._vm_name_to_ref[vm_cache.name] def _update_task_cache(self, object): task_cache = TaskCache() for change in object.changeSet: if change.op != "assign": continue if change.val is None: continue if change.name == "info.error": task_cache.error = change.val elif change.name == "info.state": task_cache.state = TaskState._NAMES_TO_VALUES[change.val] self._logger.debug("task cache update: update task [%s] => %s" % (str(object.obj), task_cache)) self._task_cache[str(object.obj)] = task_cache def _remove_task_cache(self, object): assert str(object.obj) in self._task_cache, "%s not in cache" % str( object.obj) self._logger.debug( "task cache update: remove task [%s] => %s" % (str(object.obj), self._task_cache[str(object.obj)])) del self._task_cache[str(object.obj)] def _validate_vm(self, vm): try: vm.validate() return True except: return False def _start_syncing_cache(self): self._logger.info("Start vim client sync vm cache thread") self.sync_thread = SyncVmCacheThread(self, self.wait_timeout, self.min_interval, self.stats_interval, errback=self.errback) self.sync_thread.start() def _stop_syncing_cache(self, wait=False): if self.sync_thread: self.sync_thread.stop() if wait: self.sync_thread.join() @lock_with("_task_counter_lock") def _task_counter_add(self): self._task_counter += 1 @lock_with("_task_counter_lock") def _task_counter_sub(self): self._task_counter -= 1 @lock_with("_task_counter_lock") def _task_counter_read(self): return self._task_counter @hostd_error_handler def update_hosts_stats(self): stats = self.get_perf_manager_stats() self._update_host_cache(stats) @lock_with("_host_cache_lock") def _update_host_cache(self, stats): self._host_stats = copy.copy(stats) @lock_with("_host_cache_lock") def get_host_stats(self): return copy.copy(self._host_stats) def set_large_page_support(self, disable=False): """Disables large page support on the ESX hypervisor This is done when the host memory is overcommitted. """ optionManager = self.host_system.configManager.advancedOption option = vim.OptionValue() option.key = self.ALLOC_LARGE_PAGES if disable: option.value = 0L self._logger.warning("Disabling large page support") else: option.value = 1L self._logger.warning("Enabling large page support") optionManager.UpdateOptions([option])