def read_uuid(): uuid = '' uuid_file_path = '/sys/class/dmi/id/product_uuid' try: with open(uuid_file_path) as f: uuid = f.readline().strip() except Exception as e: raise LadLoggingConfigException( 'read_uuid() failed: Unable to open uuid file {0}'.format( uuid_file_path)) if not uuid: raise LadLoggingConfigException( 'read_uuid() failed: Empty content in uuid file {0}'.format( uuid_file_path)) return uuid
def __generate_mdsd_syslog_config(self): """ Helper method to generate oms_mdsd_syslog_config """ if self._syslog_disabled: return '' # For basic syslog conf (single dest table): Source name is unified as 'mdsd.syslog' and # dest table (eventName) is 'LinuxSyslog'. This is currently the only supported syslog conf scheme. syslog_routeevents = mxt.per_RouteEvent_tmpl.format( event_name='LinuxSyslog', opt_store_type='') # Add RouteEvent elements for specified "sinks" for "syslogEvents" feature # Also add EventStreamingAnnotation for EventHub sinks syslog_eh_urls = '' for sink_name in LadUtil.getSinkList(self._syslogEvents): if sink_name == 'LinuxSyslog': raise LadLoggingConfigException( "'LinuxSyslog' can't be used as a sink name. " "It's reserved for default Azure Table name for syslog events." ) routeevent, eh_url = self.__generate_routeevent_and_eh_url_for_extra_sink( sink_name, syslog_src_name) syslog_routeevents += routeevent syslog_eh_urls += eh_url mdsd_event_source = '' if syslog_routeevents: # Do not add MdsdEventSource element if there's no associated RouteEvent generated. mdsd_event_source = mxt.per_MdsdEventSource_tmpl.format( source=syslog_src_name, routeevents=syslog_routeevents) return mxt.top_level_tmpl_for_logging_only.format( sources=mxt.per_source_tmpl.format(name=syslog_src_name), events=mdsd_event_source, eh_urls=syslog_eh_urls)
def __throw_if_output_is_none(output): """ Helper to check if output is already generated (not None) and throw if it's not (None). :return: None """ if output is None: raise LadLoggingConfigException('LadConfigAll.get_*_config() should be called after ' 'LadConfigAll.generate_mdsd_omsagent_syslog_config() is called')
def __generate_routeevent_and_eh_url_for_extra_sink( self, sink_name, src_name): """ Helper method to generate one RouteEvent element for each extra sink given. Also generates an EventStreamingAnnotation element for EventHub sinks. :param str sink_name: The name of the sink for the RouteEvent. :param str src_name: The name of the ingested source that should be used for EventStreamingAnnotation. :rtype str,str: :return: A pair of the XML RouteEvent element string for the sink and the EventHubStreamingAnnotation XML string. """ sink = self._sinksConfig.get_sink_by_name(sink_name) if not sink: raise LadLoggingConfigException( 'Sink name "{0}" is not defined in sinksConfig'.format( sink_name)) sink_type = sink['type'] if not sink_type: raise LadLoggingConfigException( 'Sink type for sink "{0}" is not defined in sinksConfig'. format(sink_name)) if sink_type == 'JsonBlob': return mxt.per_RouteEvent_tmpl.format(event_name=sink_name, opt_store_type='storeType="JsonBlob"'),\ '' # No EventStreamingAnnotation for JsonBlob elif sink_type == 'EventHub': if 'sasURL' not in sink: raise LadLoggingConfigException( 'sasURL is not specified for EventHub sink_name={0}'. format(sink_name)) # For syslog/filelogs (ingested events), the source name should be used for EventStreamingAnnotation name. eh_url = mxt.per_eh_url_tmpl.format( eh_name=src_name, key_path=self._pkey_path, enc_eh_url=self._encrypt_secret(self._cert_path, sink['sasURL'])) return '', eh_url # No RouteEvent for logging event's EventHub sink else: raise LadLoggingConfigException( '{0} sink type (for sink_name={1}) is not supported'.format( sink_type, sink_name))
def syslog_name_to_rsyslog_name(syslog_name): """ Convert a syslog name (e.g., "LOG_USER") to the corresponding rsyslog name (e.g., "user") :param str syslog_name: A syslog name for a facility (e.g., "LOG_USER") or a severity (e.g., "LOG_ERR") :rtype: str :return: Corresponding rsyslog name (e.g., "user" or "error") """ if syslog_name == '*': # We accept '*' as a facility name (also as a severity name, though it's not required) # to allow customers to collect for reserved syslog facility numeric IDs (12-15) return '*' if syslog_name not in syslog_name_to_rsyslog_name_map: raise LadLoggingConfigException( 'Invalid syslog name given: {0}'.format(syslog_name)) return syslog_name_to_rsyslog_name_map[syslog_name]
def __generate_mdsd_filelog_config(self): """ Helper method to generate oms_mdsd_filelog_config """ if not self._fileLogs: return '' # Per-file source name is 'mdsd.filelog<.path.to.file>' where '<.path.to.file>' is a full path # with all '/' replaced by '.'. filelogs_sources = '' filelogs_mdsd_event_sources = '' filelogs_eh_urls = '' for file_key in sorted(self._file_table_map): if not self._file_table_map[file_key] and not self._file_sinks_map[ file_key]: raise LadLoggingConfigException( 'Neither "table" nor "sinks" defined for file "{0}"'. format(file_key)) source_name = 'mdsd.filelog{0}'.format(file_key.replace('/', '.')) filelogs_sources += mxt.per_source_tmpl.format(name=source_name) per_file_routeevents = '' if self._file_table_map[file_key]: per_file_routeevents += mxt.per_RouteEvent_tmpl.format( event_name=self._file_table_map[file_key], opt_store_type='') if self._file_sinks_map[file_key]: for sink_name in self._file_sinks_map[file_key].split(','): routeevent, eh_url = self.__generate_routeevent_and_eh_url_for_extra_sink( sink_name, source_name) per_file_routeevents += routeevent filelogs_eh_urls += eh_url if per_file_routeevents: # Do not add MdsdEventSource element if there's no associated RouteEvent generated. filelogs_mdsd_event_sources += \ mxt.per_MdsdEventSource_tmpl.format(source=source_name, routeevents=per_file_routeevents) return mxt.top_level_tmpl_for_logging_only.format( sources=filelogs_sources, events=filelogs_mdsd_event_sources, eh_urls=filelogs_eh_urls)
def __init__(self, ext_settings, ext_dir, waagent_dir, deployment_id, fetch_uuid, encrypt_string, logger_log, logger_error): """ Constructor. :param ext_settings: A LadExtSettings (in Utils/lad_ext_settings.py) obj wrapping the Json extension settings. :param ext_dir: Extension directory (e.g., /var/lib/waagent/Microsoft.OSTCExtensions.LinuxDiagnostic-2.3.xxxx) :param waagent_dir: WAAgent directory (e.g., /var/lib/waagent) :param deployment_id: Deployment ID string (or None) that should be obtained & passed by the caller from waagent's HostingEnvironmentCfg.xml. :param fetch_uuid: A function which fetches the UUID for the VM :param encrypt_string: A function which encrypts a string, given a cert_path :param logger_log: Normal logging function (e.g., hutil.log) that takes only one param for the logged msg. :param logger_error: Error logging function (e.g., hutil.error) that takes only one param for the logged msg. """ self._ext_settings = ext_settings self._ext_dir = ext_dir self._waagent_dir = waagent_dir self._deployment_id = deployment_id self._fetch_uuid = fetch_uuid self._encrypt_secret = encrypt_string self._logger_log = logger_log self._logger_error = logger_error self._telegraf_me_url = metrics_constants.lad_metrics_extension_influx_udp_url self._telegraf_mdsd_url = metrics_constants.telegraf_influx_url self._enable_metrics_extension = False # Generated logging configs place holders self._fluentd_syslog_src_config = None self._fluentd_tail_src_config = None self._fluentd_out_mdsd_config = None self._rsyslog_config = None self._syslog_ng_config = None self._telegraf_config = None self._telegraf_namespaces = None self._mdsd_config_xml_tree = ET.ElementTree( ET.fromstring(mxt.entire_xml_cfg_tmpl)) self._sink_configs = LadUtil.SinkConfiguration() self._sink_configs.insert_from_config( self._ext_settings.read_protected_config('sinksConfig')) # If we decide to also read sinksConfig from ladCfg, do it first, so that private settings override # Get encryption settings handlerSettings = ext_settings.get_handler_settings() if handlerSettings['protectedSettings'] is None: errorMsg = "Settings did not contain protectedSettings. For information on protected settings, " \ "visit https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/diagnostics-linux#protected-settings." self._logger_error(errorMsg) raise LadLoggingConfigException(errorMsg) if handlerSettings['protectedSettingsCertThumbprint'] is None: errorMsg = "Settings did not contain protectedSettingsCertThumbprint. For information on protected settings, " \ "visit https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/diagnostics-linux#protected-settings." self._logger_error(errorMsg) raise LadLoggingConfigException(errorMsg) thumbprint = handlerSettings['protectedSettingsCertThumbprint'] self._cert_path = os.path.join(waagent_dir, thumbprint + '.crt') self._pkey_path = os.path.join(waagent_dir, thumbprint + '.prv')
def __init__(self, syslogEvents, fileLogs, sinksConfig, pkey_path, cert_path, encrypt_secret): """ Constructor to receive/store necessary LAD settings for the desired configuration generation. :param dict syslogEvents: LAD 3.0 "ladCfg" - "syslogEvents" JSON object, or a False object if it's not given in the extension settings. An example is as follows: "ladCfg": { "syslogEvents" : { "sinks": "SyslogSinkName0", "syslogEventConfiguration": { "facilityName1": "minSeverity1", "facilityName2": "minSeverity2" } } } Only the JSON object corresponding to "syslogEvents" key should be passed. facilityName1/2 is a syslog facility name (e.g., "LOG_USER", "LOG_LOCAL0"). minSeverity1/2 is a syslog severity level (e.g., "LOG_ERR", "LOG_CRIT") or "NONE". "NONE" means no logs from the facility will be captured (thus it's equivalent to not specifying the facility at all). :param dict fileLogs: LAD 3.0 "fileLogs" JSON object, or a False object if it's not given in the ext settings. An example is as follows: "fileLogs": { "fileLogConfiguration": [ { "file": "/var/log/mydaemonlog", "table": "MyDaemonEvents", "sinks": "FilelogSinkName1", }, { "file": "/var/log/myotherdaemonelog", "table": "MyOtherDaemonEvents", "sinks": "FilelogSinkName2" } ] } Only the JSON array corresponding to "fileLogConfiguration" key should be passed. "file" is the full path of the log file to be watched and captured. "table" is for the Azure storage table into which the lines of the watched file will be placed (one row per line). :param LadUtil.SinkConfiguration sinksConfig: SinkConfiguration object that's created out of "sinksConfig" LAD 3.0 JSON setting. Refer to LadUtil.SinkConfiguraiton documentation. :param str pkey_path: Path to the VM's private key that should be passed to mdsd XML for decrypting encrypted secrets (EH SAS URL) :param str cert_path: Path to the VM's certificate that should be used to encrypt secrets (EH SAS URL) :param encrypt_secret: Function to encrypt a secret (string, 2nd param) with the provided cert path param (1st) """ self._syslogEvents = syslogEvents self._fileLogs = fileLogs self._sinksConfig = sinksConfig self._pkey_path = pkey_path self._cert_path = cert_path self._encrypt_secret = encrypt_secret self._fac_sev_map = None try: # Create facility-severity map. E.g.: { "LOG_USER" : "LOG_ERR", "LOG_LOCAL0", "LOG_CRIT" } if self._syslogEvents: self._fac_sev_map = self._syslogEvents[ 'syslogEventConfiguration'] self._syslog_disabled = not self._fac_sev_map # A convenience predicate if self._fileLogs: # Convert the 'fileLogs' JSON object array into a Python dictionary of 'file' - 'table' # E.g., [{ 'file': '/var/log/mydaemonlog1', 'table': 'MyDaemon1Events', 'sinks': 'File1Sink'}, # { 'file': '/var/log/mydaemonlog2', 'table': 'MyDaemon2Events', 'sinks': 'File2SinkA,File2SinkB'}] self._file_table_map = dict([ (entry['file'], entry['table'] if 'table' in entry else '') for entry in self._fileLogs ]) self._file_sinks_map = dict([ (entry['file'], entry['sinks'] if 'sinks' in entry else '') for entry in self._fileLogs ]) self._rsyslog_config = None self._syslog_ng_config = None self._mdsd_syslog_config = None self._mdsd_telegraf_config = None self._mdsd_filelog_config = None except KeyError as e: raise LadLoggingConfigException( "Invalid setting name provided (KeyError). Exception msg: {0}". format(e))