def run(): """ Invoke install_package() and enable_and_start() as needed. """ rdebug("Run, beacon, run!") run_beacon.run() rdebug("Returning to the storpool_block setup") sputils.check_systemd_service("storpool_block") rdebug("Checking for the 'storpool' Python module") try: subprocess.check_call(["python2", "-c", "from storpool import spapi"], shell=False) except subprocess.CalledProcessError: raise sperror.StorPoolMissingComponentsException(["python2-storpool"]) rdebug("Checking for the 'storpool.spopenstack' Python module") try: subprocess.check_call( ["python2", "-c", "from storpool.spopenstack import spattachdb"], shell=False, ) except subprocess.CalledProcessError: raise sperror.StorPoolMissingComponentsException( ["python2-storpool.spopenstack"]) spstatus.npset("maintenance", "")
def check_systemd_service(name, in_lxc=False): """ Check for a systemd service with the specified name. """ service = "{name}.service".format(name=name) spstatus.npset( "maintenance", "checking for the {name} service".format(name=name), ) if in_lxc or check_in_lxc(): rdebug( "running in an LXC container, not checking for " + service, prefix=name, ) return lines = ( subprocess.check_output( ["systemctl", "show", "-p", "Type", service], shell=False, ) .decode("UTF-8") .splitlines() ) rdebug("got {lines}".format(lines=repr(lines)), prefix=name) if len(lines) != 1 or lines[0] not in ("Type=forking", "Type=simple"): raise sperror.StorPoolMissingComponentsException([service])
def write_out_config(): """ Write out the StorPool configuration file specified in the charm config. """ rdebug('about to write out the /etc/storpool.conf file') spstatus.npset('maintenance', 'updating the /etc/storpool.conf file') with tempfile.NamedTemporaryFile(dir='/tmp', mode='w+t', delete=True) as spconf: rdebug('about to write the contents to the temporary file {sp}'.format( sp=spconf.name)) templating.render( source='storpool.conf', target=spconf.name, owner='root', perms=0o600, context={ 'storpool_conf': spconfig.m()['storpool_conf'], }, ) rdebug('about to invoke txn install') txn.install('-o', 'root', '-g', 'root', '-m', '644', '--', spconf.name, '/etc/storpool.conf') rdebug('it seems that /etc/storpool.conf has been created') rdebug('trying to read it now') spconfig.drop_cache() cfg = spconfig.get_dict() oid = cfg['SP_OURID'] spconfig.set_our_id(oid) rdebug('got {len} keys in the StorPool config, our id is {oid}'.format( len=len(cfg), oid=oid)) rdebug('setting the config-written state') reactive.set_state('l-storpool-config.config-written') spstatus.npset('maintenance', '')
def config_changed(): """ Check if the configuration is complete or has been changed. """ rdebug('config-changed happened') reactive.remove_state('l-storpool-config.configure') config = spconfig.m() # Remove any states that say we have accomplished anything... for state in STATES_REDO['unset']: reactive.remove_state(state) spconfig.unset_our_id() spconf = config.get('storpool_conf', None) rdebug('and we do{xnot} have a storpool_conf setting'.format( xnot=' not' if spconf is None else '')) if spconf is None or spconf == '': return # And let's make sure we try installing any packages we need... reactive.set_state('l-storpool-config.config-available') reactive.set_state('l-storpool-config.package-try-install') # This will probably race with some others, but oh well spstatus.npset( 'maintenance', 'waiting for the StorPool charm configuration and ' 'the StorPool repo setup')
def not_ready_no_config(): """ Note that some configuration settings are missing. """ rdebug('well, it seems we have a repo, but we do not have a config yet') spstatus.npset('maintenance', 'waiting for the StorPool charm configuration')
def first_install(): """ On initial installation, note that we need to collect and submit the data. """ rdebug('install invoked, triggering both a recollection and ' 'a resubmission') reactive.set_state('storpool-inventory.collecting') reactive.remove_state('storpool-inventory.collected') reactive.set_state('storpool-inventory.submitting') reactive.remove_state('storpool-inventory.submitted') spstatus.npset('maintenance', 'setting up')
def do_install_apt_repo(): """ Check and, if necessary, add the StorPool repository. """ rdebug('install-apt-repo invoked') spstatus.npset('maintenance', 'checking for the APT repository') if not has_apt_repo(): install_apt_repo() rdebug('install-apt-repo seems fine') spstatus.npset('maintenance', '') reactive.set_state('storpool-repo-add.installed-apt-repo')
def do_install_apt_key(): """ Check and, if necessary, install the StorPool package signing key. """ rdebug('install-apt-key invoked') spstatus.npset('maintenance', 'checking for the APT key') if not has_apt_key(): install_apt_key() rdebug('install-apt-key seems fine') spstatus.npset('maintenance', '') reactive.set_state('storpool-repo-add.installed-apt-key')
def enable_and_start(): """ Run the StorPool OpenStack integration on the current node and, if configured, its LXD containers. """ if not hookenv.config()["storpool_openstack_install"]: rdebug("skipping the installation into containers") return # to do: check for the nova-compute and cinder-volume services # to do: set up the groups spstatus.npset("maintenance", "")
def do_update_apt(): """ Invoke `apt-get update` to fetch data from the StorPool repository. """ rdebug('invoking apt-get update') spstatus.npset('maintenance', 'updating the APT cache') subprocess.check_call(['apt-get', 'update']) rdebug('update-apt seems fine') spstatus.npset('maintenance', '') reactive.set_state('storpool-repo-add.updated-apt') # And, finally, the others can do stuff, too reactive.set_state('storpool-repo-add.available')
def stop(): """ Clean up and no longer attempt to install anything. """ rdebug('storpool-repo-add stopping as requested') for fname in (apt_sources_list(), apt_keyring()): if os.path.isfile(fname): rdebug('- trying to remove {name}'.format(name=fname)) try: os.unlink(fname) except Exception as e: rdebug(' - could not remove {name}: {e}' .format(name=fname, e=e)) else: rdebug('- no {name} to remove'.format(name=fname)) for state in STATES_REDO['set'] + STATES_REDO['unset']: reactive.remove_state(state) reactive.remove_state('storpool-repo-add.stop') reactive.set_state('storpool-repo-add.stopped') spstatus.npset('maintenance', '')
def try_to_submit(): """ Once the data has been collected and `submit_url` is set, go ahead. """ url = hookenv.config().get('submit_url', None) rdebug('trying to submit to {url}'.format(url=url)) reactive.remove_state('storpool-inventory.submitting') if url is None: rdebug('erm, how did we get here with no submit URL?') return spstatus.npset('maintenance', 'submitting the collected data') try: global datafile rdebug('about to read {df}'.format(df=datafile)) with open(datafile, mode='r', encoding='latin1') as f: contents = ''.join(f.readlines()) rdebug('read {ln} characters of data from the collect file'.format( ln=len(contents))) data = json.dumps({'filename': platform.node(), 'contents': contents}) rdebug('encoded stuff into {ln} characters of data to submit'.format( ln=len(data))) data_enc = data.encode('latin1') rdebug('submitting {ln} bytes of data to {url}'.format( ln=len(data_enc), url=url)) with urllib.request.urlopen(url, data=data_enc) as resp: rdebug('got some kind of an HTTP response') code = resp.getcode() rdebug('got response code {code}'.format(code=code)) if code is not None and code >= 200 and code < 300: rdebug('success!') reactive.set_state('storpool-inventory.submitted') spstatus.set('active', 'here, have a blob of data') except Exception as e: rdebug('could not submit the data: {e}'.format(e=e)) sputils.err('failed to submit the collected data')
def setup_interfaces(): """ Set up the IPv4 addresses of some interfaces if requested. """ if sputils.check_in_lxc(): rdebug('running in an LXC container, not setting up interfaces') reactive.set_state('l-storpool-config.config-network') return rdebug('trying to parse the StorPool interface configuration') spstatus.npset('maintenance', 'parsing the StorPool interface configuration') cfg = spconfig.get_dict() ifaces = cfg.get('SP_IFACE', None) if ifaces is None: hookenv.set('error', 'No SP_IFACES in the StorPool config') return rdebug('got interfaces: {ifaces}'.format(ifaces=ifaces)) spcnetwork.fixup_interfaces(ifaces) rdebug('well, looks like it is all done...') reactive.set_state('l-storpool-config.config-network') spstatus.npset('maintenance', '')
def copy_config_files(): """ Install some configuration files. """ spstatus.npset('maintenance', 'copying the storpool-common config files') basedir = '/usr/lib/storpool/etcfiles/storpool-common' for f in ( '/etc/rsyslog.d/99-StorPool.conf', '/etc/sysctl.d/99-StorPool.conf', ): rdebug('installing {fname}'.format(fname=f)) txn.install('-o', 'root', '-g', 'root', '-m', '644', basedir + f, f) rdebug('about to restart rsyslog') spstatus.npset('maintenance', 'restarting the system logging service') host.service_restart('rsyslog') reactive.set_state('storpool-common.config-written') spstatus.npset('maintenance', '')
def have_config(): """ Check whether the `submit_url` configuration parameter has been set or changed; if so, trigger a collect-and-submit cycle. """ rdebug('config-changed') config = hookenv.config() url = config.get('submit_url', None) if url is not None and url != '': if config.changed('submit_url') or \ not rhelpers.is_state('storpool-inventory.configured'): spstatus.reset() reactive.set_state('storpool-inventory.configured') rdebug( 'we have a new submission URL address: {url}'.format(url=url)) reactive.set_state('storpool-inventory.submitting') reactive.remove_state('storpool-inventory.submitted') if not rhelpers.is_state('storpool-inventory.collected') and \ not rhelpers.is_state('storpool-inventory.collecting'): rdebug('triggering another collection attempt') spstatus.npset('maintenance', 'about to try to collect data again') reactive.set_state('storpool-inventory.collecting') else: spstatus.npset('maintenance', 'about to resubmit any collected data') else: rdebug('the submission URL address seems to be the same as before') else: rdebug('we do not seem to have a submission URL address') reactive.remove_state('storpool-inventory.configured') reactive.remove_state('storpool-inventory.submitting') reactive.remove_state('storpool-inventory.submitted') spstatus.reset() spstatus.npset('maintenance', 'waiting for configuration')
def install_package(): """ Install the StorPool block package. """ rdebug('the block repo has become available and the common packages ' 'have been configured') if sputils.check_in_lxc(): rdebug('running in an LXC container, not doing anything more') reactive.set_state('storpool-block.package-installed') return spstatus.npset('maintenance', 'obtaining the requested StorPool version') spver = spconfig.m().get('storpool_version', None) if spver is None or spver == '': rdebug('no storpool_version key in the charm config yet') return spstatus.npset('maintenance', 'installing the StorPool block packages') (err, newly_installed) = sprepo.install_packages({ 'storpool-block': spver, }) if err is not None: rdebug('oof, we could not install packages: {err}'.format(err=err)) rdebug('removing the package-installed state') return if newly_installed: rdebug('it seems we managed to install some packages: {names}'.format( names=newly_installed)) sprepo.record_packages('storpool-block', newly_installed) else: rdebug('it seems that all the packages were installed already') rdebug('setting the package-installed state') reactive.set_state('storpool-block.package-installed') spstatus.npset('maintenance', '')
def install_package(): """ Install the base StorPool packages. """ rdebug('the repo hook has become available and ' 'we do have the configuration') spstatus.npset('maintenance', 'obtaining the requested StorPool version') spver = spconfig.m().get('storpool_version', None) if spver is None or spver == '': rdebug('no storpool_version key in the charm config yet') return spstatus.npset('maintenance', 'installing the StorPool configuration packages') reactive.remove_state('l-storpool-config.package-try-install') (err, newly_installed) = sprepo.install_packages({ 'txn-install': '*', 'storpool-config': spver, }) if err is not None: rdebug('oof, we could not install packages: {err}'.format(err=err)) rdebug('removing the package-installed state') reactive.remove_state('l-storpool-config.package-installed') return if newly_installed: rdebug('it seems we managed to install some packages: {names}'.format( names=newly_installed)) sprepo.record_packages('storpool-config', newly_installed) else: rdebug('it seems that all the packages were installed already') rdebug('setting the package-installed state') reactive.set_state('l-storpool-config.package-installed') spstatus.npset('maintenance', '')
def collect(): """ Generate and run a shell script invoking various system tools to collect some information. """ spstatus.reset() rdebug('about to collect some data, are we not') reactive.remove_state('storpool-inventory.collecting') spstatus.npset('maintenance', 'installing packages for data collection') try: (err, newly_installed) = sprepo.install_packages({ 'dmidecode': '*', 'lshw': '*', 'nvme-cli': '*', 'pciutils': '*', 'usbutils': '*', }) if err is not None: raise Exception('{e}'.format(e=err)) if newly_installed: rdebug('it seems we installed some new packages: {lst}'.format( lst=' '.join(newly_installed))) else: rdebug('it seems we already had everything we needed') sprepo.record_packages('storpool-inventory-charm', newly_installed) spstatus.npset('maintenance', '') except Exception as e: sputils.err('failed to install the OS packages') return spstatus.npset('maintenance', 'collecting data') try: with tempfile.TemporaryDirectory(dir='/tmp', prefix='storpool-inventory.') as d: rdebug('created a temporary directory {d}'.format(d=d)) """ No need to create a working directory for the present... workname = 'collect-' + platform.node() workdir = d + '/' + workname os.mkdir(workdir, mode=0o700) rdebug('created the working directory {w}'.format(w=workdir)) """ workdir = d collect_script = workdir + '/collect.sh' with open(collect_script, mode='w') as f: print(collect_commands.format(w=workdir), end='', file=f) os.chmod(collect_script, 0o700) rdebug('running the collect script'.format(cs=collect_script)) subprocess.call([ 'sh', '-c', "{cs} > '{w}/collect.txt' 2>'{w}/collect.err'".format( cs=collect_script, w=workdir) ]) collected = {} rdebug('scanning the {w} directory now'.format(w=workdir)) for e in os.scandir(workdir): if not e.is_file(): continue rdebug('- {name}'.format(name=e.name)) with open(workdir + '/' + e.name, mode='r', encoding='latin1') as f: collected[e.name] = ''.join(f.readlines()) rdebug('collected {ln} entries: {ks}'.format( ln=len(collected), ks=sorted(collected.keys()))) data = json.dumps(collected) rdebug('and dumped them to {ln} characters of data'.format( ln=len(data))) global datafile rdebug('about to write {df}'.format(df=datafile)) if not os.path.isdir(datadir): os.mkdir(datadir, mode=0o700) with open(datafile, mode='w', encoding='latin1') as f: rdebug('about to write to the file') print(data, file=f) rdebug('done writing to the file, it seems') rdebug('about to check the size of the collect file') st = os.stat(datafile) rdebug('it seems we wrote {ln} bytes to the file'.format( ln=st.st_size)) rdebug('we seem to be done here!') reactive.set_state('storpool-inventory.collected') spstatus.npset('maintenance', '') except Exception as e: rdebug('something bad happened: {e}'.format(e=e)) sputils.err('maintenance', 'failed to collect the data')
def not_ready_no_repo(): """ Note that the `storpool-repo` layer has not yet completed its work. """ rdebug('well, it seems we have a config, but we do not have a repo yet') spstatus.npset('maintenance', 'waiting for the StorPool repo setup')
def install_package(): """ Install the base StorPool packages. """ rdebug('the common repo has become available and ' 'we do have the configuration') rdebug('checking the kernel command line') with open('/proc/cmdline', mode='r') as f: ln = f.readline() if not ln: sputils.err('Could not read a single line from /proc/cmdline') return words = ln.split() # OK, so this is a bit naive, but it will do the job global KERNEL_REQUIRED_PARAMS missing = list( filter(lambda param: param not in words, KERNEL_REQUIRED_PARAMS)) if missing: if sputils.bypassed('kernel_parameters'): hookenv.log( 'The "kernel_parameters" bypass is meant FOR ' 'DEVELOPMENT ONLY! DO NOT run a StorPool cluster ' 'in production with it!', hookenv.WARNING) else: sputils.err('Missing kernel parameters: {missing}'.format( missing=' '.join(missing))) return spstatus.npset('maintenance', 'obtaining the requested StorPool version') spver = spconfig.m().get('storpool_version', None) if spver is None or spver == '': rdebug('no storpool_version key in the charm config yet') return spstatus.npset('maintenance', 'installing the StorPool common packages') (err, newly_installed) = sprepo.install_packages({ 'storpool-cli': spver, 'storpool-common': spver, 'storpool-etcfiles': spver, 'kmod-storpool-' + os.uname().release: spver, 'python-storpool': spver, }) if err is not None: rdebug('oof, we could not install packages: {err}'.format(err=err)) rdebug('removing the package-installed state') return if newly_installed: rdebug('it seems we managed to install some packages: {names}'.format( names=newly_installed)) sprepo.record_packages('storpool-common', newly_installed) else: rdebug('it seems that all the packages were installed already') rdebug('updating the kernel module dependencies') spstatus.npset('maintenance', 'updating the kernel module dependencies') subprocess.check_call(['depmod', '-a']) rdebug('gathering CPU information for the cgroup configuration') with open('/proc/cpuinfo', mode='r') as f: lns = f.readlines() all_cpus = sorted( map( lambda lst: int(lst[2]), filter(lambda lst: lst and lst[0] == 'processor', map(lambda s: s.split(), lns)))) if sputils.bypassed('very_few_cpus'): hookenv.log( 'The "very_few_cpus" bypass is meant ' 'FOR DEVELOPMENT ONLY! DO NOT run a StorPool cluster in ' 'production with it!', hookenv.WARNING) last_cpu = all_cpus[-1] all_cpus.extend([last_cpu, last_cpu, last_cpu]) if len(all_cpus) < 4: sputils.err('Not enough CPUs, need at least 4') return tdata = { 'cpu_rdma': str(all_cpus[0]), 'cpu_beacon': str(all_cpus[1]), 'cpu_block': str(all_cpus[2]), 'cpu_rest': '{min}-{max}'.format(min=all_cpus[3], max=all_cpus[-1]), } rdebug('gathering system memory information for the cgroup configuration') with open('/proc/meminfo', mode='r') as f: while True: line = f.readline() if not line: sputils.err('Could not find MemTotal in /proc/meminfo') return words = line.split() if words[0] == 'MemTotal:': mem_total = int(words[1]) unit = words[2].upper() if unit.startswith('K'): mem_total = int(mem_total / 1024) elif unit.startswith('M'): pass elif unit.startswith('G'): mem_total = mem_total * 1024 else: sputils.err('Could not parse the "{u}" unit for ' 'MemTotal in /proc/meminfo'.format(u=words[2])) return break mem_system = 4 * 1024 mem_user = 4 * 1024 mem_storpool = 1 * 1024 mem_kernel = 10 * 1024 if sputils.bypassed('very_little_memory'): hookenv.log( 'The "very_little_memory" bypass is meant ' 'FOR DEVELOPMENT ONLY! DO NOT run a StorPool cluster in ' 'production with it!', hookenv.WARNING) mem_system = 1 * 1900 mem_user = 1 * 512 mem_storpool = 1 * 1024 mem_kernel = 1 * 512 mem_reserved = mem_system + mem_user + mem_storpool + mem_kernel if mem_total <= mem_reserved: sputils.err( 'Not enough memory, only have {total}M, need {mem}M'.format( mem=mem_reserved, total=mem_total)) return mem_machine = mem_total - mem_reserved tdata.update({ 'mem_system': mem_system, 'mem_user': mem_user, 'mem_storpool': mem_storpool, 'mem_machine': mem_machine, }) rdebug('generating the cgroup configuration: {tdata}'.format(tdata=tdata)) if not os.path.isdir('/etc/cgconfig.d'): os.mkdir('/etc/cgconfig.d', mode=0o755) cgconfig_dir = '/usr/share/doc/storpool/examples/cgconfig/ubuntu1604' for (path, _, files) in os.walk(cgconfig_dir): for fname in files: src = path + '/' + fname dst = src.replace(cgconfig_dir, '') dstdir = os.path.dirname(dst) if not os.path.isdir(dstdir): os.makedirs(dstdir, mode=0o755) if fname in ( 'machine.slice.conf', 'storpool.slice.conf', 'system.slice.conf', 'user.slice.conf', 'machine-cgsetup.conf', ): with tempfile.NamedTemporaryFile(dir='/tmp', mode='w+t', delete=True) as tempf: rdebug('- generating {tempf} for {dst}'.format( dst=dst, tempf=tempf.name)) templating.render( source=fname, target=tempf.name, owner='root', perms=0o644, context=tdata, ) rdebug('- generating {dst}'.format(dst=dst)) txn.install('-o', 'root', '-g', 'root', '-m', '644', '--', tempf.name, dst) else: mode = '{:o}'.format(os.stat(src).st_mode & 0o777) rdebug('- installing {src} as {dst}'.format(src=src, dst=dst)) txn.install('-o', 'root', '-g', 'root', '-m', mode, '--', src, dst) rdebug('starting the cgconfig service') rdebug('- refreshing the systemctl service database') subprocess.check_call(['systemctl', 'daemon-reload']) rdebug('- starting the cgconfig service') try: host.service_resume('cgconfig') except Exception: pass rdebug('setting the package-installed state') reactive.set_state('storpool-common.package-installed') spstatus.npset('maintenance', '')
def run(): rdebug("Run, OpenStack integration, run!") run_osi.run() rdebug("Returning to the storpool-beacon setup") sputils.check_systemd_service("storpool_beacon") spstatus.npset("maintenance", "")
def report_no_config(): """ Note that the `storpool_repo_url` has not been set yet. """ rdebug('no StorPool configuration yet') spstatus.npset('maintenance', 'waiting for the StorPool configuration')