Esempio n. 1
0
def down(compose_args):
    """Stop all services.

    This wraps `docker-compose down`
    """
    utils.check_config()
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh(f'{sudo}docker-compose down ' + ' '.join(list(compose_args)))
Esempio n. 2
0
def down():
    """Stop all services.

    This wraps `docker-compose down --remove-orphans`
    """
    utils.check_config()
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh('{}docker-compose down --remove-orphans'.format(sudo))
Esempio n. 3
0
def kill():
    """Stop and remove all containers on this computer.

    This includes those not from Brewblox.
    """
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh('{}docker rm --force $({}docker ps -aq)'.format(sudo, sudo),
       check=False)
Esempio n. 4
0
def read_fields(policy, measurement, keys):
    prefix = 'm_' * POLICIES.index(policy)
    fields = ','.join(['"{}{}"'.format(prefix, k)
                       for k in keys])

    utils.info('Reading {} {}'.format(measurement, policy))
    sh('docker-compose exec influx influx -format csv ' +
       "-execute 'SELECT {} from brewblox.{}.\"{}\"'".format(fields, policy, measurement) +
       '> /tmp/influx_rename_{}.csv'.format(policy))
Esempio n. 5
0
def up(compose_args):
    """Start all services.

    This wraps `docker-compose up -d`
    """
    utils.check_config()
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh(f'{sudo}docker-compose up -d ' + ' '.join(list(compose_args)))
Esempio n. 6
0
def kill(zombies):
    """Stop and remove all containers on this host.

    This includes those not from Brewblox.

    If the --zombies flag is set,
    leftover processes that are still claiming a port will be forcibly removed.
    Use this if you get "port is already allocated" errors after your system crashed.
    """
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh(f'{sudo}docker rm --force $({sudo}docker ps -aq)', check=False)

    if zombies:
        # We can't use psutil for this, as we need root rights to get pids
        if not utils.command_exists('netstat'):
            utils.warn(
                'Command `netstat` not found. Please install it by running:')
            utils.warn('')
            utils.warn(
                '    sudo apt-get update && sudo apt-get install net-tools')
            utils.warn('')
            return

        procs = re.findall(r'(\d+)/docker-proxy',
                           sh('sudo netstat -pna', capture=True))

        if procs:
            utils.info(f'Removing {len(procs)} zombies...')
            sh('sudo service docker stop')
            sh([f'sudo kill -9 {proc}' for proc in procs])
            sh('sudo service docker start')
Esempio n. 7
0
def prepare_flasher(release, pull):
    tag = utils.docker_tag(release)
    sudo = utils.optsudo()

    if pull:
        utils.info('Pulling flasher image...')
        sh('{}docker pull brewblox/firmware-flasher:{}'.format(sudo, tag))

    if utils.path_exists('./docker-compose.yml'):
        utils.info('Stopping services...')
        sh('{}docker-compose down'.format(sudo))
Esempio n. 8
0
def restart():
    """Stop and start all services.

    This wraps `docker-compose down --remove-orphans; docker-compose up -d`

    Note: `docker-compose restart` also exists -
    it restarts containers without recreating them.
    """
    utils.check_config()
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh('{}docker-compose down --remove-orphans'.format(sudo))
    sh('{}docker-compose up -d'.format(sudo))
Esempio n. 9
0
def restart(compose_args):
    """Recreates all services.

    This wraps `docker-compose up -d --force-recreate`

    Note: `docker-compose restart` also exists -
    it restarts containers without recreating them.
    """
    utils.check_config()
    utils.confirm_mode()
    sudo = utils.optsudo()
    sh(f'{sudo}docker-compose up -d --force-recreate ' +
       ' '.join(list(compose_args)))
Esempio n. 10
0
def get_keys(measurement, pattern):
    retv = sh('docker-compose exec influx influx -format json ' +
              "-execute 'SHOW FIELD KEYS ON brewblox FROM brewblox.downsample_1m.\"{}\"'".format(measurement),
              capture=True)
    values = json.loads(retv)['results'][0]['series'][0]['values']
    keys = []
    for (k, t) in values:
        key = re.sub('m_', '', k, count=1)
        if re.match(pattern, key):
            keys.append(key)
    return keys
Esempio n. 11
0
def follow(services):
    """Show logs for one or more services.

    This will start watching the logs for specified services.
    Call without arguments to show logs for all running services.

    Once started, press ctrl+C to stop.

    Service name will be equal to those specified in docker-compose.log,
    not the container name.

    To follow logs for service 'spark-one':

    \b
        GOOD: `brewblox-ctl follow spark-one`
         BAD: `brewblox-ctl follow brewblox_spark-one_1`
    """
    utils.check_config()
    sudo = utils.optsudo()
    sh('{}docker-compose logs --follow {}'.format(sudo, ' '.join(services)))
Esempio n. 12
0
def disable_ipv6():
    """Disable IPv6 support on the host machine.

    Reason: https://github.com/docker/for-linux/issues/914
    Should only be used if your services are having stability issues
    """
    utils.confirm_mode()
    is_disabled = sh('cat /proc/sys/net/ipv6/conf/all/disable_ipv6', capture=True).strip()
    if is_disabled == '1':
        utils.info('IPv6 is already disabled')
    elif is_disabled == '0' or utils.ctx_opts().dry_run:
        utils.info('Disabling IPv6...')
        sh('echo "net.ipv6.conf.all.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf')
        sh('echo "net.ipv6.conf.default.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf')
        sh('echo "net.ipv6.conf.lo.disable_ipv6 = 1" | sudo tee -a /etc/sysctl.conf')
        sh('sudo sysctl -p')
    else:
        utils.info('Invalid result when checking IPv6 status: ' + is_disabled)
Esempio n. 13
0
def save(file, force):
    """Save Brewblox directory to snapshot.

    This can be used to move Brewblox installations between hosts.
    To load the snapshot, use `brewblox-ctl install --snapshot ARCHIVE`
    or `brewblox-ctl snapshot load --file ARCHIVE`

    Block data stored on Spark controllers is not included in the snapshot.
    """
    utils.check_config()
    utils.confirm_mode()
    dir = Path('./').resolve()

    if utils.path_exists(file):
        if force or utils.confirm(f'`{file}` already exists. ' +
                                  'Do you want to overwrite it?'):
            sh(f'rm -f {file}')
        else:
            return

    sh(f'sudo tar -C {dir.parent} --exclude .venv -czf {file} {dir.name}')
    click.echo(Path(file).resolve())
Esempio n. 14
0
def load(file):
    """Create Brewblox directory from snapshot.

    This can be used to move Brewblox installations between hosts.
    To create a snapshot, use `brewblox-ctl snapshot save`
    """
    utils.check_config()
    utils.confirm_mode()
    dir = Path('./').resolve()

    with TemporaryDirectory() as tmpdir:
        utils.info(f'Extracting snapshot to {dir} directory...')
        sh(f'tar -xzf {file} -C {tmpdir}')
        content = list(Path(tmpdir).iterdir())
        if utils.ctx_opts().dry_run:
            content = ['brewblox']
        if len(content) != 1:
            raise ValueError(f'Multiple files found in snapshot: {content}')
        sh('sudo rm -rf ./*')
        # We need to explicitly include dotfiles in the mv glob
        src = content[0]
        sh(f'mv {src}/.[!.]* {src}/* {dir}/')

    actions.install_ctl_package(download='missing')
Esempio n. 15
0
def write_fields(policy, keys, pattern, replace):
    prefix = 'm_' * POLICIES.index(policy)
    fields = [re.sub(pattern, replace, k, count=1) for k in keys]
    fields = [re.sub(r' ', r'\\ ', k) for k in fields]

    infile = '/tmp/influx_rename_{}.csv'.format(policy)
    outfile = '/tmp/influx_rename_{}.line'.format(policy)
    sh('rm {}'.format(outfile), check=False)

    with open(infile) as f_in:
        if not f_in.readline():
            utils.info('No values found in policy "{}"'.format(policy))
            return

        with open(outfile, 'w') as f_out:
            f_out.write('# DML\n')
            f_out.write('# CONTEXT-DATABASE: brewblox\n')
            f_out.write('# CONTEXT-RETENTION-POLICY: {}\n'.format(policy))
            f_out.write('\n')

            while True:
                line = f_in.readline().strip()
                if not line:
                    break
                values = line.split(',')
                measurement = values.pop(0)
                time = values.pop(0)
                data = ','.join(['{}{}={}'.format(prefix, field, value)
                                 for (field, value) in zip(fields, values)
                                 if value and value != '0'])
                if data:
                    f_out.write('{} {} {}\n'.format(measurement, data, time))

    utils.info('Writing {} {}'.format(measurement, policy))
    sh('docker cp {} $(docker-compose ps -q influx):/rename'.format(outfile))
    sh('docker-compose exec influx influx -import -path=/rename || true')
Esempio n. 16
0
def test_sh(mocker):
    m_run = mocker.patch(TESTED + '.run')
    m_secho = mocker.patch(TESTED + '.click.secho')
    m_opts = mocker.patch(TESTED + '.ctx_opts').return_value

    m_opts.dry_run = False
    m_opts.verbose = False

    # Single call
    utils.sh('do things')
    assert m_secho.call_count == 0
    assert m_run.call_count == 1
    m_run.assert_called_with('do things',
                             shell=True,
                             check=True,
                             universal_newlines=False,
                             stdout=None,
                             stderr=STDOUT)

    m_run.reset_mock()
    m_secho.reset_mock()

    # Unchecked call
    utils.sh('do naughty things', check=False)
    assert m_secho.call_count == 0
    assert m_run.call_count == 1
    m_run.assert_called_with('do naughty things',
                             shell=True,
                             check=False,
                             universal_newlines=False,
                             stdout=None,
                             stderr=DEVNULL)

    m_run.reset_mock()
    m_secho.reset_mock()

    # Captured call
    utils.sh('gimme gimme', capture=True)
    assert m_secho.call_count == 0
    assert m_run.call_count == 1
    m_run.assert_called_with('gimme gimme',
                             shell=True,
                             check=True,
                             universal_newlines=True,
                             stdout=PIPE,
                             stderr=STDOUT)

    m_run.reset_mock()
    m_secho.reset_mock()

    # Dry run
    utils.sh('invisible shenannigans', utils.ContextOpts(dry_run=True))
    assert m_secho.call_count == 1
    assert m_run.call_count == 0

    m_run.reset_mock()
    m_secho.reset_mock()

    # List of commands
    utils.sh(['do this', 'and this', 'and that'])
    assert m_secho.call_count == 0
    assert m_run.call_count == 3

    m_run.reset_mock()
    m_secho.reset_mock()

    # Generator
    def generate():
        yield 'first'
        yield 'second'

    m_run.reset_mock()
    utils.sh(generate(), utils.ContextOpts(verbose=True))
    assert m_secho.call_count == 2
    assert m_run.call_count == 2
    m_run.assert_any_call('first',
                          shell=True,
                          check=True,
                          universal_newlines=False,
                          stdout=None,
                          stderr=STDOUT)
    m_run.assert_called_with('second',
                             shell=True,
                             check=True,
                             universal_newlines=False,
                             stdout=None,
                             stderr=STDOUT)
Esempio n. 17
0
def run_flasher(release, args):
    tag = utils.docker_tag(release)
    sudo = utils.optsudo()
    opts = '-it --rm --privileged -v /dev:/dev'
    sh('{}docker run {} brewblox/firmware-flasher:{} {}'.format(sudo, opts, tag, args))
Esempio n. 18
0
def install(use_defaults,
            apt_install,
            docker_install,
            docker_user,
            no_reboot,
            dir,
            release):
    """Create Brewblox directory; install system dependencies; reboot.

    Brewblox can be installed multiple times on the same computer.
    Settings and databases are stored in a Brewblox directory (default: ./brewblox).

    This command also installs system-wide dependencies (docker).
    After `brewblox-ctl install`, run `brewblox-ctl setup` in the created Brewblox directory.

    A reboot is required after installing docker, or adding the user to the 'docker' group.

    By default, `brewblox-ctl install` attempts to download packages using the apt package manager.
    If you are using a system without apt (eg. Synology NAS), this step will be skipped.
    You will need to manually install any missing libraries.

    \b
    Steps:
        - Install apt packages.
        - Install docker.
        - Add user to 'docker' group.
        - Create Brewblox directory (default ./brewblox).
        - Set variables in .env file.
        - Reboot.
    """
    utils.confirm_mode()

    apt_deps = 'curl net-tools libssl-dev libffi-dev'
    user = utils.getenv('USER')
    default_dir = path.abspath('./brewblox')
    prompt_reboot = True

    if use_defaults is None:
        use_defaults = utils.confirm('Do you want to install with default settings?')

    # Check if packages should be installed
    if not utils.command_exists('apt'):
        utils.info('Apt is not available. You may need to find another way to install dependencies.')
        utils.info('Apt packages: "{}"'.format(apt_deps))
        apt_install = False

    if apt_install is None:
        if use_defaults:
            apt_install = True
        else:
            apt_install = utils.confirm('Do you want to install apt packages "{}"?'.format(apt_deps))

    # Check if docker should be installed
    if utils.command_exists('docker'):
        utils.info('Docker is already installed.')
        docker_install = False

    if docker_install is None:
        if use_defaults:
            docker_install = True
        else:
            docker_install = utils.confirm('Do you want to install docker?')

    # Check if user should be added to docker group
    if utils.is_docker_user():
        utils.info('{} already belongs to the docker group.'.format(user))
        docker_user = False

    if docker_user is None:
        if use_defaults:
            docker_user = True
        else:
            docker_user = utils.confirm('Do you want to run docker commands without sudo?')

    # Check used directory
    if dir is None:
        if use_defaults or utils.confirm("The default directory is '{}'. Do you want to continue?".format(default_dir)):
            dir = default_dir
        else:
            return

    if utils.path_exists(dir):
        if not utils.confirm('{} already exists. Do you want to continue?'.format(dir)):
            return

    if not no_reboot:
        prompt_reboot = utils.confirm('A reboot is required after installation. ' +
                                      'Do you want to be prompted before that happens?')

    # Install Apt packages
    if apt_install:
        utils.info('Installing apt packages...')
        sh([
            'sudo apt update',
            'sudo apt upgrade -y',
            'sudo apt install -y {}'.format(apt_deps),
        ])
    else:
        utils.info('Skipped: apt install.')

    # Install docker
    if docker_install:
        utils.info('Installing docker...')
        sh('curl -sL get.docker.com | sh')
    else:
        utils.info('Skipped: docker install.')

    # Add user to 'docker' group
    if docker_user:
        utils.info("Adding {} to 'docker' group...".format(user))
        sh('sudo usermod -aG docker $USER')
    else:
        utils.info("Skipped: adding {} to 'docker' group.".format(user))

    # Create install directory
    utils.info('Creating Brewblox directory ({})...'.format(dir))
    sh('mkdir -p {}'.format(dir))

    # Set variables in .env file
    utils.info('Setting variables in .env file...')
    dotenv_path = path.abspath('{}/.env'.format(dir))
    sh('touch {}'.format(dotenv_path))
    utils.setenv(const.RELEASE_KEY, release, dotenv_path)
    utils.setenv(const.CFG_VERSION_KEY, '0.0.0', dotenv_path)
    utils.setenv(const.SKIP_CONFIRM_KEY, str(use_defaults), dotenv_path)

    utils.info('Done!')

    # Reboot
    if not no_reboot:
        if prompt_reboot:
            utils.info('Press ENTER to reboot.')
            input()
        else:
            utils.info('Rebooting in 10 seconds...')
            sleep(10)
        sh('sudo reboot')
    else:
        utils.info('Skipped: reboot.')
Esempio n. 19
0
def install(ctx: click.Context, snapshot_file):
    """Install Brewblox and its dependencies.

    Brewblox can be installed multiple times on the same computer.
    Settings and databases are stored in a Brewblox directory.

    This command also installs system-wide dependencies.
    A reboot is required after installing docker, or adding the user to the 'docker' group.

    By default, `brewblox-ctl install` attempts to download packages using the apt package manager.
    If you are using a system without apt (eg. Synology NAS), this step will be skipped.
    You will need to manually install any missing libraries.

    When using the `--snapshot ARCHIVE` option, no dir is created.
    Instead, the directory in the snapshot is extracted.
    It will be renamed to the desired name of the Brewblox directory.

    \b
    Steps:
        - Ask confirmation for installation steps.
        - Install apt packages.
        - Install docker.
        - Add user to 'docker' group.
        - Fix host IPv6 settings.
        - Disable host-wide mDNS reflection.
        - Set variables in .env file.
        - If snapshot provided:
            - Load configuration from snapshot.
        - Else:
            - Check for port conflicts.
            - Create docker-compose configuration files.
            - Create datastore (Redis) directory.
            - Create history (Victoria) directory.
            - Create gateway (Traefik) directory.
            - Create SSL certificates.
            - Create eventbus (Mosquitto) directory.
            - Set version number in .env file.
        - Pull docker images.
        - Reboot if needed.
    """
    utils.confirm_mode()
    user = utils.getenv('USER')
    opts = InstallOptions()

    opts.check_confirm_opts()
    opts.check_system_opts()
    opts.check_docker_opts()
    opts.check_reboot_opts()

    if not snapshot_file:
        opts.check_init_opts()

    # Install Apt packages
    if opts.apt_install:
        utils.info('Installing apt packages...')
        apt_deps = ' '.join(const.APT_DEPENDENCIES)
        sh([
            'sudo apt-get update',
            'sudo apt-get upgrade -y',
            f'sudo apt-get install -y {apt_deps}',
        ])
    else:
        utils.info('Skipped: apt-get install.')

    # Install docker
    if opts.docker_install:
        utils.info('Installing docker...')
        sh('curl -sL get.docker.com | sh', check=False)
    else:
        utils.info('Skipped: docker install.')

    # Add user to 'docker' group
    if opts.docker_group_add:
        utils.info(f"Adding {user} to 'docker' group...")
        sh('sudo usermod -aG docker $USER')
    else:
        utils.info(f"Skipped: adding {user} to 'docker' group.")

    # Always apply actions
    actions.disable_ssh_accept_env()
    actions.fix_ipv6(None, False)
    actions.edit_avahi_config()
    actions.add_particle_udev_rules()
    actions.uninstall_old_ctl_package()
    actions.deploy_ctl_wrapper()

    # Set variables in .env file
    # Set version number to 0.0.0 until snapshot load / init is done
    utils.info('Setting .env values...')
    utils.setenv(const.CFG_VERSION_KEY, '0.0.0')
    utils.setenv(const.SKIP_CONFIRM_KEY, str(opts.skip_confirm))
    for key, default_val in const.ENV_DEFAULTS.items():
        utils.setenv(key, utils.getenv(key, default_val))

    # Install process splits here
    # Either load all config files from snapshot or run init
    sudo = utils.optsudo()
    if snapshot_file:
        ctx.invoke(snapshot.load, file=snapshot_file)
    else:
        release = utils.getenv('BREWBLOX_RELEASE')

        utils.info('Checking for port conflicts...')
        actions.check_ports()

        utils.info('Copying docker-compose.shared.yml...')
        sh(f'cp -f {const.CONFIG_DIR}/docker-compose.shared.yml ./')

        if opts.init_compose:
            utils.info('Copying docker-compose.yml...')
            sh(f'cp -f {const.CONFIG_DIR}/docker-compose.yml ./')

        # Stop after we're sure we have a compose file
        utils.info('Stopping services...')
        sh(f'{sudo}docker-compose down')

        if opts.init_datastore:
            utils.info('Creating datastore directory...')
            sh('sudo rm -rf ./redis/; mkdir ./redis/')

        if opts.init_history:
            utils.info('Creating history directory...')
            sh('sudo rm -rf ./victoria/; mkdir ./victoria/')

        if opts.init_gateway:
            utils.info('Creating gateway directory...')
            sh('sudo rm -rf ./traefik/; mkdir ./traefik/')

            utils.info('Creating SSL certificate...')
            actions.makecert('./traefik', release)

        if opts.init_eventbus:
            utils.info('Creating mosquitto config directory...')
            sh('sudo rm -rf ./mosquitto/; mkdir ./mosquitto/')

        # Always copy cert config to traefik dir
        sh(f'cp -f {const.CONFIG_DIR}/traefik-cert.yaml ./traefik/')

        # Init done - now set CFG version
        utils.setenv(const.CFG_VERSION_KEY, const.CURRENT_VERSION)

    if opts.docker_pull:
        utils.info('Pulling docker images...')
        sh(f'{sudo}docker-compose pull')

    utils.info('All done!')

    # Reboot
    if opts.reboot_needed:
        if opts.prompt_reboot:
            utils.info('Press ENTER to reboot.')
            input()
        else:
            utils.info('Rebooting in 10 seconds...')
            sleep(10)
        sh('sudo reboot')