Hallo, dies ist ein Test.
PWD: /www/data-lst1/unixsoft/unixsoft/kaempfer/.public_html
Running in File Mode
Relative path: ./../../../../../././../lib/svc/method/svc-sstore
Real path: /lib/svc/method/svc-sstore
Zurück
#!/usr/bin/python3.7 -Es import solaris.no_site_packages # # Copyright (c) 2013, 2023, Oracle and/or its affiliates. # # start and stop methods for statistics store import multiprocessing import os import re import smf_include import subprocess import sys import time import rad.connect as radcon import rad.client as radclient import rad.bindings.com.oracle.solaris.rad.bemgr_1 as bemgr import rad.bindings.com.oracle.solaris.rad.zfsmgr_1 as zfsmgr SSTOREBIN = "/usr/lib/sstore/bin" SVCPROP = "/usr/bin/svcprop" SVCS = "/usr/bin/svcs" SSTORE = "/usr/bin/sstore" SSTOREADM = "/usr/bin/sstoreadm" SSTORED = os.path.join(SSTOREBIN, "sstored") ZFS = "/usr/sbin/zfs" # The dataset suffix should be kept in sync with SSLOG_DATASET_SUFFIX # defined in sstore_log.c SSTORE_DS_SUFFIX = "/VARSHARE/sstore" SS_ERR_TYPE = ( SS_ERR_OK, SS_ERR_FATAL, SS_ERR_CONFIG, SS_ERR_LIVE_CD ) = list(range(4)) # sstored exit codes SSTORED_ERR_CODES = ( SSEC_TYPE_OK, SSEC_TYPE_CONFIG, SSEC_TYPE_INTERNAL, SSEC_TYPE_VERSION ) = list(range(4)) def check_contract(fmri): """check output of svcs -p to make sure subprocess and start method are running""" lines = subprocess.check_output( [SVCS, "-Hpo", "nstate", fmri]).splitlines() # if service is transitioning, nstate will not be '-'; # do not fail if service is transitioning even if sstore # is not running. try: nstate = lines[0].split() except (ValueError, IndexError) as err: print("Unable to determine service state", file=sys.stderr) return False # remaining lines are processes in contract: STIME PID CMD for line in lines[1:]: try: if line.split()[2] == "sstored": return True except IndexError as err: pass # fail if sstored is not found and the service is not transitioning, # otherwise let the caller try again if nstate == '-': print("sstored not found", file=sys.stderr) return False else: return True def quiet_check(seconds=10, success_desired=True, fmri="svc:/system/sstore:default"): """function to poll stat store for either success or failure; will always check once even if seconds == 0; returns amount of time waited or None if desired state was not reached. Check to make sure stat store is alive after daemonization, too.""" wait_time = 0 with open(os.devnull, "w") as devnull: while True: try: subprocess.check_call([SSTORE, "list", "//:class.cpu"], stderr=devnull, stdout=devnull) if success_desired: return wait_time else: time.sleep(1) wait_time += 1 if wait_time > seconds: return except subprocess.CalledProcessError as err: if not success_desired: return wait_time else: time.sleep(1) wait_time += 1 if wait_time > seconds: return if not check_contract(fmri): return def repo_convert(repo_root): """recursivly converts stat repositories to newer versions on first daemon exec after upgrade""" if not repo_root: print("Error occurred while converting repo: No repo-path", file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL # First, lookup current version. try: old_ver = subprocess.check_output( [os.path.join(SSTOREBIN, "repo_ver"), repo_root]).splitlines()[0] except subprocess.CalledProcessError as err: print("Error occurred while converting repo: can't read repo version", file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL # Run conversion script script_path = os.path.join(SSTOREBIN, "upgrade_" + old_ver.decode()) try: subprocess.check_call([script_path, repo_root], stdout=sys.stdout, stderr=sys.stderr) except subprocess.CalledProcessError as err: print("Error occurred while converting repo: Upgrade failure", file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL # Make sure version actually changed (so we don't infinitely recurse) try: new_ver = subprocess.check_output( [os.path.join(SSTOREBIN, "repo_ver"), repo_root]).splitlines()[0] except subprocess.CalledProcessError as err: print("Error occurred after converting repo: can't read repo version", file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL if new_ver <= old_ver: print("Error occurred after converting repo: {0} {1} <= {2}".format( "new version less than or equal to old", new_ver, old_ver)) return smf_include.SMF_EXIT_ERR_FATAL # Attempt to start sstored. return (start()) def check_mnttab(mount_point): """Checks if there is any dataset mounted at the given mountpoint. Returns: (dataset name, filesystem type) on success, (None, None) if no dataset is mounted at the mountpoint Note that /etc/mnttab is NOT a file, but a file-like representation of a filesystem. We are following the definition noted in mnttab(5) to identify the information we need when we split each line. """ # when this code is converted to Python 3, test with utf-8 mountpoints with open("/etc/mnttab", "r") as f: for line in f: try: dataset, mountp, fstype = line.split("\t", 4)[0:3] if mountp == mount_point: return dataset, fstype except Exception: pass return None, None def get_rpool_info(): """Find the rpool name and rpool version Returns: (rpool name, rpool version)""" with radcon.connect_private(daemon_suffix=radcon.RAD_DAEMON_39) as rc: try: rpool_name = rc.get_object(bemgr.BEManager()).getActiveBE().zpool name_pattern = radclient.ADRGlobPattern({'name': rpool_name}) rpool = rc.get_object(zfsmgr.Zpool(), name_pattern) rpool_props = [zfsmgr.ZfsPropRequest(name='version')] rpool_version = int(rpool.get_props(rpool_props)[0].value) except radclient.ObjectError as error: raise SystemExit("Failed to initialize sstore dataset." " %s" % error.get_payload().info) return (rpool_name, rpool_version) def dataset_update(ds_name, mountpoint, compression): """Creates the sstore dataset if it doesn't exist already. Returns: SS_ERR_TYPE value""" with radcon.connect_private() as rc: ds_props = [zfsmgr.ZfsProp(name='compression', value=compression), zfsmgr.ZfsProp(name='mountpoint', value=mountpoint), zfsmgr.ZfsProp(name='canmount', value='on')] try: name_pattern = radclient.ADRGlobPattern({'name': ds_name}) sstoreds = rc.get_object(zfsmgr.ZfsDataset(), name_pattern) compr_prop = [zfsmgr.ZfsPropRequest(name='compression')] if sstoreds.get_props(compr_prop)[0].value == compression: raise radclient.ExistsError("compression exists") except radclient.ExistsError: pass except radclient.NotFoundError: # we only want to attempt to create the leaf sstore name if all # of <rpool>/VARSHARE is missing we should fail. varshare = '/'.join(ds_name.split('/')[:-1]) vs_pattern = radclient.ADRGlobPattern({'name': varshare}) try: vsds = rc.get_object(zfsmgr.ZfsDataset(), vs_pattern) sstoreds = vsds.create_filesystem(name=ds_name, props=ds_props) except (radclient.ObjectError, radclient.NotFoundError) as err: print('Failed to create {0}: {1}'.format(ds_name, err)) else: try: # Try to set properties # Continue with original properties in case of failure sstoreds.set_props(ds_props) except radclient.PrivError: print("Insufficient Privileges to set {0} compression \ property".format(compression)) pass except radclient.ObjectError as error: errstr = error.get_payload().libzfs_errstr print("Failed to set compression={0} due to {1}". format(compression, errstr)) pass # It is possible a dataset was previously created with canmount=noauto # and is present here but unmounted. If so mount it here. ds_mount = [zfsmgr.ZfsPropRequest(name='mounted')] if sstoreds.get_props(ds_mount)[0].value == 'no': sstoreds.mount(ds_name) def dataset_init(repo_path): """Initialize the sstore dataset. Returns: (SS_ERR_TYPE value, Dataset name)""" # Get the rpool name and version try: rpool_name, rpool_version = get_rpool_info() sstore_ds = rpool_name + SSTORE_DS_SUFFIX # lz4 compression is the preferred compression algorithm for the # sstore repo. If the rpool version is older than the version # which introduced lz4 compression (37) use gzip. if rpool_version >= 37: compression = 'lz4' else: compression = 'gzip' except Exception: compression = 'gzip' # Create the dataset if it doesn't exist dataset_update(sstore_ds, repo_path, compression) ds_name, fstype = check_mnttab(repo_path) if fstype is not None: if fstype == "zfs" and sstore_ds == ds_name: # dataset is already mounted; nothing to do here return (SS_ERR_OK, sstore_ds) else: # some other filesystem is using the repo path as its mountpoint print("The repo_path {0} is being used as a mountpoint for " "dataset {1}".format(repo_path, ds_name), file=sys.stderr) return (SS_ERR_CONFIG, sstore_ds) else: # dataset_update() should have set the proprties so the repo is mounted # if not, return an error return (SS_ERR_CONFIG, sstore_ds) return (SS_ERR_OK, sstore_ds) def get_repo_cap(): """Computes the max repo capacity based on the number of CPUs in the system. repo cap (in MB) = (n_cpu * 32) if n_cpu > 64 = 2048 if n_cpu <= 64 The above heuristic was determined by running sstore on multiple test machines with default collections left enabled. The results of that experiment showed a direct correlation between repo growth rate and # of CPUs. In the future, if we add more collections to the default set, this may need to be tweaked. It also assumes 3 interval granularities (second, minute and hour) for logged data. If that changes, the multiplier constant in the equation should be updated. With the current heuristic, if only the default collections are left enabled, it should not use up more than 20 percent of the repo capacity.""" n_cpu = multiprocessing.cpu_count() if n_cpu <= 64: repo_cap = 2048 else: repo_cap = n_cpu * 32 return repo_cap def get_available_space(sstore_ds): """Returns the maximum space available (in MB) for the given dataset""" args = [ZFS, "list", "-H", "-p", "-o", "avail", sstore_ds] try: # Get available space in MB return int(subprocess.check_output(args).splitlines()[0]) / 1048576 except subprocess.CalledProcessError as err: print("Error occurred while reading available space in {0}".format( sstore_ds), file=sys.stderr) return None def start(): """start method gathers the config options and execs sstore""" fmri = os.getenv("SMF_FMRI") if fmri is None: # "cannot" happen print("SMF_FMRI is None: script must be invoked as SMF method", file=sys.stderr) return smf_include.SMF_EXIT_ERR_CONFIG # get line by line output from svcprop and process into # argument list try: cfgprops_ba = subprocess.check_output( [SVCPROP, "-p", "config", fmri]).splitlines() except subprocess.CalledProcessError as err: cfgprops = [p.decode() for p in cfgprops_ba] print("Error occurred w/ svcprop: '{0}'".format(cfgprops), file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL cfgprops = [p.decode() for p in cfgprops_ba] # build argument list for sstore (long-term turn events on by default) args = [SSTORED, "--events"] repo_root = None repo_cap = None use_sbrk = None for line in cfgprops: if line != "": name, dummy, value = line.split(" ", 2) # Remove 'config/' from name prop = name[7:] # Switch our umem-backend to sbrk if prop == 'use-sbrk': use_sbrk = True # This isn't a daemon option, so don't append to args continue if prop == 'repo-path': # Grab the repo-path if we come across it repo_root = value elif prop == 'max-repo-size': repo_cap = int(value) for term in re.split(r"(?<!\\)\s", value): args.append("--{0}".format(prop)) # Remove any leftover backslashes in term args.append(term.replace("\\", "")) # initialize the sstore dataset err, sstore_ds = dataset_init(repo_root) if err != SS_ERR_OK: if err == SS_ERR_LIVE_CD: args.append("--in-memory") elif err == SS_ERR_CONFIG: return smf_include.SMF_EXIT_ERR_CONFIG else: print("Failed to initialize the sstore dataset", file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL # Compute repo capacity if it isn't configured degrade_msg = None if repo_cap is None: repo_cap = get_repo_cap() # Make sure we have enough space available in the dataset avail = get_available_space(sstore_ds) if avail is None or avail < repo_cap: # Run in memory since we don't know or have enough space if avail is None: degrade_msg = ("Couldn't figure out available space for {0}, " "running in memory".format(sstore_ds)) else: degrade_msg = ("Running in memory because dataset {0} doesn't " "have enough space. Need: {1} MB, Available: " "{2:.2f} MB".format(sstore_ds, repo_cap, avail)) args += ["--in-memory"] else: args += ["--max-repo-size", str(repo_cap)] # use check_call method; sstored will daemonize when invoked by smf # this way we get errors before it forks # # it is easy to extend environment for debugging/testing purposes. # for instance, to run with libadimalloc and adiheap: # # env = os.environ.copy() # env['LD_PRELOAD_64'] = '/lib/64/libadimalloc.so' # args = '/usr/sbin/sxadm exec -i -s adiheap=enable'.split(' ') + args # # and pass env to check_call(). env = os.environ.copy() # By default run sstored with the mmap backend of libumem. # This was done to allow sstored to shrink in size after ballooning while # servicing stressful user-queries. if not use_sbrk: env['UMEM_OPTIONS'] = 'backend=mmap' try: subprocess.check_call(args, env=env, stdout=sys.stdout, stderr=sys.stderr) except subprocess.CalledProcessError as err: if err.returncode == SSEC_TYPE_CONFIG: print("Configuration Error while executing {}: {}". format(args, err), file=sys.stderr) return smf_include.SMF_EXIT_ERR_CONFIG elif err.returncode == SSEC_TYPE_INTERNAL: print("Invalid user while executing {}: {}". format(args, err), file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL elif err.returncode == SSEC_TYPE_VERSION: # Repo version mismatch, attempt to convert old -> new version. print("Attempting repo upgrade", file=sys.stderr) return (repo_convert(repo_root)) else: print("Internal Error while executing {}: {}".format(args, err), file=sys.stderr) return smf_include.SMF_EXIT_ERR_FATAL # sstored will take a few seconds to be ready to go; don't # return until we can talk to it. if quiet_check(seconds=300, success_desired=True, fmri=fmri) is not None: # Transition to degraded if we had to switch to "in-memory" mode if degrade_msg is not None: smf_include.smf_method_exit(smf_include.SMF_EXIT_DEGRADED, "not_enough_space", degrade_msg) return smf_include.SMF_EXIT_OK return smf_include.SMF_EXIT_ERR_FATAL def stop(): """Stop method; have sstore halt asap""" # is it running at all? results = quiet_check(seconds=0, success_desired=False) if results is not None: return smf_include.SMF_EXIT_OK # shut it down subprocess.check_output([SSTOREADM, "exit"]) # wait for a bit if we need to if quiet_check(seconds=120, success_desired=False) is not None: return smf_include.SMF_EXIT_OK return smf_include.SMF_EXIT_ERR_FATAL smf_include.smf_main()