def connect(self, exit_on_fault=True): """ Connect to VCenter. Currently doesn't use certificate, as it not used in the most installations or self-signed used. :param bool exit_on_fault: Perform exit on connection fault if True, otherwise returns None. :return: VMWare ServiceInstance object or None in case of connection fault. """ logging.info(f'Connecting to {self._address} ...') try: si = SmartConnectNoSSL(host=self._address, user=self._username, pwd=self._password) self.si = si self.content = si.RetrieveServiceContent() atexit.register(Disconnect, si) logging.info(f'Connected to {self._address}') return si except vim.fault.InvalidLogin as e: logging.info(e.msg) if exit_on_fault: exit(1) else: return None except Exception: logging.info('Unable to connect. Check address and credentials.') if exit_on_fault: exit(1) else: return None
class Environment(object): def __init__(self, config): """ Reads from configuration, connects to vCenter and starts inventory manager and metric manager threads. :param config: Configuration for the environment. """ self._host = config['host'] self._username = config['username'] self._password = config['password'] self._vc_name = config['Name'] self._ingest_token = config['IngestToken'] self._ingest_endpoint = config['IngestEndpoint'] self._ingest_timeout = config['IngestTimeout'] self._logger = logging.getLogger(self.get_instance_id()) self._si = None self._connect() if self._si is None: raise ValueError("Unable to connect to host") self._ingest = self._create_signalfx_ingest() if self._ingest is None: raise ValueError("Unable to create ingest client") self._additional_dims = config.get('dimensions', None) if 'MORSyncInterval' not in config: config['MORSyncInterval'] = constants.DEFAULT_MOR_SYNC_INTERVAL self._mor_sync_timeout = config.get('MORSyncTimeout', constants.DEFAULT_MOR_SYNC_TIMEOUT) self._metric_sync_timeout = config.get( 'MetricSyncTimeout', constants.DEFAULT_METRIC_SYNC_TIMEOUT) self._inventory_mgr = inventory.InventoryManager( self._si, config['MORSyncInterval'], config['Name'], self.get_instance_id()) self._inventory_mgr.start() if 'MetricSyncInterval' not in config: config[ 'MetricSyncInterval'] = constants.DEFAULT_METRIC_SYNC_INTERVAL self._metric_conf = self._get_metric_config(config) self._metric_mgr = metric_metadata.MetricManager( self._si, config['MetricSyncInterval'], self._metric_conf, config['Name'], self.get_instance_id()) self._metric_mgr.start() self._wait_for_sync() def _wait_for_sync(self): """ Waits until the inventory and available metrics are synced. :return: null """ if not self._inventory_mgr.block_until_inventory( timeout=self._mor_sync_timeout): raise RuntimeError( "Did not sync inventory within {0} seconds".format( self._mor_sync_timeout)) if not self._metric_mgr.block_until_has_metrics( timeout=self._metric_sync_timeout): raise RuntimeError( "Did not sync metrics within {0} seconds".format( self._metric_sync_timeout)) def _connect(self): """ Connect to the vCenter. :return: null """ try: self._si = SmartConnectNoSSL(host=self._host, user=self._username, pwd=self._password) except Exception as e: self._logger.error("Unable to connect to host {0} : {1}".format( self._host, e)) self._si = None def get_instance_id(self): """ Returns the instance id for logging. :return: string """ return "{0}-{1}".format(self._vc_name, self._host) def _get_metric_config(self, config): """ Gets the required metric preferences from Configuration. :param config: :return: dict """ metric_config = dict() metric_config['include_metrics'] = config.get('include_metrics', {}) metric_config['exclude_metrics'] = config.get('exclude_metrics', {}) return metric_config def _create_signalfx_ingest(self): """ Creates and returns the SignalFX ingest client. :return: Ingest Client """ ingest = None try: client = signalfx.SignalFx() ingest = client.ingest(self._ingest_token, endpoint=self._ingest_endpoint, timeout=self._ingest_timeout) except Exception as e: self._logger.error( "An error occured when creating the ingest client: {0}".format( e)) return ingest def _get_dimensions(self, inv_obj, metric_value): """ Returns the dimensions of inventory object. :param inv_obj: Inventory Object. eg: host, vm etc :param metric_value: Value of inventory object metric. :return: dict """ dimensions = {} if self._additional_dims is not None: dimensions.update(self._additional_dims) dimensions.update(inv_obj.sf_metadata_dims) if metric_value.id.instance != '': instance = str(metric_value.id.instance).replace(':', '_'). \ replace('.', '_') dimensions['instance'] = instance return dimensions def _parse_query(self, inv_obj, query_results, monitored_metrics): """ Parses the query results, builds and returns datapoints. :param inv_obj: Inventory Object :param query_results: Query results from QueryPerf(). :param monitored_metrics: Metrics which will be monitored by the application for inventory object. :return: list """ datapoints = [] timestamp = int(time.time()) * 1000 try: result = query_results[0] for metric in result.value: key = metric.id.counterId metric_name = monitored_metrics[key].name metric_type = monitored_metrics[key].metric_type dimensions = self._get_dimensions(inv_obj, metric) value = metric.value[0] if monitored_metrics[key].units == 'percent': value /= 100.0 dp = self.Datapoint(metric_name, metric_type, value, dimensions, timestamp) datapoints.append(dp) except Exception as e: self._logger.error( "Error while parsing query results: {0} : {1}".format( query_results, e)) return datapoints def _build_payload(self, dps): """ Builds a ingest client payload from the datapoints. :param dps: datapoints :return: dict """ dp_count = len(dps) payload = [] start = 0 delta = 100 end = delta if dp_count > delta else dp_count try: for x in range(0, int(dp_count / delta) + 1): gauges = [] counters = [] for dp in dps[start:end]: dp.dimensions['metric_source'] = constants.METRIC_SOURCE payload_obj = { 'metric': dp.metric_name, 'value': dp.value, 'dimensions': dp.dimensions, 'timestamp': dp.timestamp } if dp.metric_type == 'gauge': gauges.append(payload_obj) elif dp.metric_type == 'counter': counters.append(payload_obj) payload.append({'gauges': gauges, 'counters': counters}) start = end end = end + delta if end > dp_count: end = dp_count except Exception as e: self._logger.error( "Exception while building payload : {0}".format(e)) return payload def _dispatch_metrics(self, payload): """ Dispatches metrics to signalfx client. :param payload: Ingest client payload(contains the datapoints) :return: null """ for item in payload: try: self._ingest.send(gauges=item['gauges'], counters=item['counters']) except Exception as e: self._logger.error( "Exception while sending payload to ingest : {0}".format( e)) def read_metric_values(self): """ Collects the required metrics for all inventory objects from vCenter and dispatches them to Ingest client. :return: null """ inv_objs = self._inventory_mgr.current_inventory() monitored_metrics = self._metric_mgr.get_monitored_metrics() perf_manager = self._si.RetrieveServiceContent().perfManager for mor in inv_objs.keys(): for inv_obj in inv_objs[mor]: inv_obj_metrics = inv_obj.metric_id_map desired_keys = list( set(inv_obj_metrics.keys()) & set(monitored_metrics[mor].keys())) if not len(desired_keys) == 0: metric_id_objs = [ inv_obj_metrics[key] for key in desired_keys ] query_spec = vim.PerformanceManager.QuerySpec( entity=inv_obj.mor, metricId=metric_id_objs, intervalId=inv_obj.INSTANT_INTERVAL, maxSample=1, format='normal') try: results = perf_manager.QueryPerf( querySpec=[query_spec]) except Exception as e: self._logger.error( "Exception while making performance query : {0}". format(e)) if results: dps = self._parse_query(inv_obj, results, monitored_metrics[mor]) payload = self._build_payload(dps) self._dispatch_metrics(payload) else: self._logger.warning( "Empty result from query : ".format(query_spec)) def stop_managers(self): """ Stops inventory manager and metric manager threads. :return: null """ self._inventory_mgr.stop() self._metric_mgr.stop() self._inventory_mgr.join(timeout=constants.DEFAULT_TIMEOUT) self._metric_mgr.join(timeout=constants.DEFAULT_TIMEOUT) class Datapoint(object): """ Plain Object to hold metric as a datapoint. """ def __init__(self, metric_name, metric_type, value, dimensions, timestamp): self.metric_name = metric_name self.metric_type = metric_type self.value = value self.dimensions = dimensions self.timestamp = timestamp