Пример #1
0
def migrate_user(old_username, new_username, password, superuser=False):
    if postgresql.is_primary():
        # We do this on any primary, as the master is
        # appointed later. It also works if we have
        # a weird setup with manual_replication and
        # multiple primaries.
        con = postgresql.connect()
        postgresql.ensure_user(con,
                               new_username,
                               password,
                               superuser=superuser)
        cur = con.cursor()
        hookenv.log("Granting old role {} to new role {}"
                    "".format(old_username, new_username))
        cur.execute(
            "GRANT %s TO %s",
            (
                postgresql.pgidentifier(old_username),
                postgresql.pgidentifier(new_username),
            ),
        )
        con.commit()
    else:
        hookenv.log("Primary must map role {!r} to {!r}"
                    "".format(old_username, new_username))
Пример #2
0
def set_active():
    if postgresql.is_running():
        if replication.is_master():
            msg = "Live master"
        elif postgresql.is_primary():
            msg = "Live primary"
        else:
            msg = "Live secondary"
        status_set("active", "{} ({})".format(msg, postgresql.point_version()))
    else:
        # PostgreSQL crashed! Maybe bad configuration we failed to
        # pick up, or maybe a full disk. The admin will need to diagnose.
        status_set("blocked", "PostgreSQL unexpectedly shut down")
Пример #3
0
def set_active():
    if postgresql.is_running():
        if replication.is_master():
            msg = 'Live master'
        elif postgresql.is_primary():
            msg = 'Live primary'
        else:
            msg = 'Live secondary'
        status_set('active', '{} ({})'.format(msg, postgresql.point_version()))
    else:
        # PostgreSQL crashed! Maybe bad configuration we failed to
        # pick up, or maybe a full disk. The admin will need to diagnose.
        status_set('blocked', 'PostgreSQL unexpectedly shut down')
Пример #4
0
def wal_e_backup(params):
    if not postgresql.is_primary():
        hookenv.action_fail("Not a primary. Run this action on the master")
        return

    backup_cmd = wal_e.wal_e_backup_command()
    if params["prune"]:
        prune_cmd = wal_e.wal_e_prune_command()
    else:
        prune_cmd = None

    hookenv.action_set({
        "wal-e-backup-cmd": backup_cmd,
        "wal-e-prune-cmd": prune_cmd
    })

    try:
        hookenv.log("Running wal-e backup")
        hookenv.log(backup_cmd)
        subprocess.check_call(
            "sudo -Hu postgres -- " + backup_cmd,
            stderr=subprocess.STDOUT,
            shell=True,
            universal_newlines=True,
        )
        hookenv.action_set({"backup-return-code": 0})
    except subprocess.CalledProcessError as x:
        hookenv.action_set({"backup-return-code": x.returncode})
        hookenv.action_fail("Backup failed")
        return

    if prune_cmd is None:
        return

    try:
        hookenv.log("Running wal-e prune")
        hookenv.log(prune_cmd)
        subprocess.check_call(
            "sudo -Hu postgres -- " + prune_cmd,
            stderr=subprocess.STDOUT,
            shell=True,
            universal_newlines=True,
        )
        hookenv.action_set({"prune-return-code": 0})
    except subprocess.CalledProcessError as x:
        hookenv.action_set({"prune-return-code": x.returncode})
        hookenv.action_fail("Backup succeeded, pruning failed")
        return
Пример #5
0
def migrate_user(old_username, new_username, password, superuser=False):
    if postgresql.is_primary():
        # We do this on any primary, as the master is
        # appointed later. It also works if we have
        # a weird setup with manual_replication and
        # multiple primaries.
        con = postgresql.connect()
        postgresql.ensure_user(con, new_username, password,
                               superuser=superuser)
        cur = con.cursor()
        hookenv.log('Granting old role {} to new role {}'
                    ''.format(old_username, new_username))
        cur.execute('GRANT %s TO %s',
                    (postgresql.pgidentifier(old_username),
                        postgresql.pgidentifier(new_username)))
        con.commit()
    else:
        hookenv.log('Primary must map role {!r} to {!r}'
                    ''.format(old_username, new_username))
Пример #6
0
def update_postgresql_crontab():
    config = hookenv.config()
    data = dict(config)

    data['scripts_dir'] = helpers.scripts_dir()
    data['is_master'] = replication.is_master()
    data['is_primary'] = postgresql.is_primary()

    if config['wal_e_storage_uri']:
        data['wal_e_enabled'] = True
        data['wal_e_backup_command'] = wal_e.wal_e_backup_command()
        data['wal_e_prune_command'] = wal_e.wal_e_prune_command()
    else:
        data['wal_e_enabled'] = False

    destination = os.path.join(helpers.cron_dir(), 'juju-postgresql')
    templating.render('postgres.cron.tmpl', destination, data,
                      owner='root', group='postgres',
                      perms=0o640)
Пример #7
0
def db_relation_common(rel):
    """Publish unit specific relation details."""
    local = rel.local
    if "database" not in local:
        return  # Not yet ready.

    # Version number, allowing clients to adjust or block if their
    # expectations are not met.
    local["version"] = postgresql.version()

    # Calculate the state of this unit. 'standalone' will disappear
    # in a future version of this interface, as this state was
    # only needed to deal with race conditions now solved by
    # Juju leadership. We check for is_primary() rather than
    # the postgresql.replication.is_master reactive state to
    # publish the correct state when we are using manual replication
    # (there might be multiple independent masters, possibly useful for
    # sharding, or perhaps this is a multi master BDR setup).
    if postgresql.is_primary():
        if reactive.helpers.is_state("postgresql.replication.has_peers"):
            local["state"] = "master"
        else:
            local["state"] = "standalone"
    else:
        local["state"] = "hot standby"

    # Host is the private ip address, but this might change and
    # become the address of an attached proxy or alternative peer
    # if this unit is in maintenance.
    local["host"] = hookenv.unit_private_ip()

    # Port will be 5432, unless the user has overridden it or
    # something very weird happened when the packages where installed.
    local["port"] = str(postgresql.port())

    # The list of remote units on this relation granted access.
    # This is to avoid the race condition where a new client unit
    # joins an existing client relation and sees valid credentials,
    # before we have had a chance to grant it access.
    local["allowed-units"] = " ".join(unit for unit, relinfo in rel.items() if "private-address" in relinfo)
Пример #8
0
def start():
    status_set('maintenance', 'Starting PostgreSQL')
    postgresql.start()

    while postgresql.is_primary() and postgresql.is_in_recovery():
        status_set('maintenance', 'Startup recovery')
        time.sleep(1)

    store = unitdata.kv()

    open_ports(store.get('postgresql.cluster.pgconf.live.port') or 5432,
               store.get('postgresql.cluster.pgconf.current.port') or 5432)

    # Update the 'live' config now we know it is in effect. This
    # is used to detect future config changes that require a restart.
    settings = store.getrange('postgresql.cluster.pgconf.current.', strip=True)
    store.unsetrange(prefix='postgresql.cluster.pgconf.live.')
    store.update(settings, prefix='postgresql.cluster.pgconf.live.')

    reactive.set_state('postgresql.cluster.is_running')
    reactive.remove_state('postgresql.cluster.needs_restart')
    reactive.remove_state('postgresql.cluster.needs_reload')
Пример #9
0
def start():
    status_set("maintenance", "Starting PostgreSQL")
    postgresql.start()

    while postgresql.is_primary() and postgresql.is_in_recovery():
        status_set("maintenance", "Startup recovery")
        time.sleep(1)

    store = unitdata.kv()

    open_ports(
        store.get("postgresql.cluster.pgconf.live.port") or 5432,
        store.get("postgresql.cluster.pgconf.current.port") or 5432,
    )

    # Update the 'live' config now we know it is in effect. This
    # is used to detect future config changes that require a restart.
    settings = store.getrange("postgresql.cluster.pgconf.current.", strip=True)
    store.unsetrange(prefix="postgresql.cluster.pgconf.live.")
    store.update(settings, prefix="postgresql.cluster.pgconf.live.")

    reactive.set_state("postgresql.cluster.is_running")
    reactive.remove_state("postgresql.cluster.needs_restart")
    reactive.remove_state("postgresql.cluster.needs_reload")
Пример #10
0
def update_postgresql_crontab():
    config = hookenv.config()
    data = dict(config)

    data["scripts_dir"] = helpers.scripts_dir()
    data["is_master"] = replication.is_master()
    data["is_primary"] = postgresql.is_primary()

    if config["wal_e_storage_uri"]:
        data["wal_e_enabled"] = True
        data["wal_e_backup_command"] = wal_e.wal_e_backup_command()
        data["wal_e_prune_command"] = wal_e.wal_e_prune_command()
    else:
        data["wal_e_enabled"] = False

    destination = os.path.join(helpers.cron_dir(), "juju-postgresql")
    templating.render(
        "postgres.cron.tmpl",
        destination,
        data,
        owner="root",
        group="postgres",
        perms=0o640,
    )
Пример #11
0
def db_relation_common(rel):
    """Publish unit specific relation details."""
    local = rel.local
    if "database" not in local:
        return  # Not yet ready.

    # Version number, allowing clients to adjust or block if their
    # expectations are not met.
    local["version"] = postgresql.version()

    # Calculate the state of this unit. 'standalone' will disappear
    # in a future version of this interface, as this state was
    # only needed to deal with race conditions now solved by
    # Juju leadership. We check for is_primary() rather than
    # the postgresql.replication.is_master reactive state to
    # publish the correct state when we are using manual replication
    # (there might be multiple independent masters, possibly useful for
    # sharding, or perhaps this is a multi master BDR setup).
    if postgresql.is_primary():
        if reactive.helpers.is_state("postgresql.replication.has_peers"):
            local["state"] = "master"
        else:
            local["state"] = "standalone"
    else:
        local["state"] = "hot standby"

    # Host is the private ip address, but this might change and
    # become the address of an attached proxy or alternative peer
    # if this unit is in maintenance.
    local["host"] = ingress_address(local.relname, local.relid)

    # Port will be 5432, unless the user has overridden it or
    # something very weird happened when the packages where installed.
    local["port"] = str(postgresql.port())

    # The list of remote units on this relation granted access.
    # This is to avoid the race condition where a new client unit
    # joins an existing client relation and sees valid credentials,
    # before we have had a chance to grant it access.
    local["allowed-units"] = " ".join(unit for unit, relinfo in rel.items() if len(incoming_addresses(relinfo)) > 0)

    # The list of IP address ranges on this relation granted access.
    # This will replace allowed-units, which does not work with cross
    # model ralations due to the anonymization of the external client.
    local["allowed-subnets"] = ",".join(
        sorted({r: True for r in chain(*[incoming_addresses(relinfo) for relinfo in rel.values()])}.keys())
    )

    # v2 protocol. Publish connection strings for this unit and its peers.
    # Clients should use these connection strings in favour of the old
    # host, port, database settings. A single proxy unit can thus
    # publish several end points to clients.
    master = replication.get_master()
    if replication.is_master():
        master_relinfo = local
    else:
        master_relinfo = rel.peers.get(master)
    local["master"] = relinfo_to_cs(master_relinfo)
    if rel.peers:
        all_relinfo = rel.peers.values()
    all_relinfo = list(rel.peers.values()) if rel.peers else []
    all_relinfo.append(rel.local)
    standbys = filter(
        None,
        [relinfo_to_cs(relinfo) for relinfo in all_relinfo if relinfo.unit != master],
    )
    local["standbys"] = "\n".join(sorted(standbys)) or None
Пример #12
0
 def test_is_primary(self, is_secondary):
     is_secondary.return_value = True
     self.assertFalse(postgresql.is_primary())
     is_secondary.return_value = False
     self.assertTrue(postgresql.is_primary())
Пример #13
0
 def test_is_primary(self, is_secondary):
     is_secondary.return_value = True
     self.assertFalse(postgresql.is_primary())
     is_secondary.return_value = False
     self.assertTrue(postgresql.is_primary())
Пример #14
0
def update_replication_states():
    """
    Set the following states appropriately:

        postgresql.replication.has_peers

            This unit has peers.

        postgresql.replication.had_peers

            This unit once had peers, but may not any more. The peer
            relation exists.

        postgresql.replication.master.peered

            This unit is peered with the master. It is not the master.

        postgresql.replication.master.authorized

            This unit is peered with and authorized by the master. It is
            not the master.

        postgresql.replication.is_master

            This unit is the master.

        postgresql.replication.has_master

            This unit is the master, or it is peered with and
            authorized by the master.

        postgresql.replication.cloned

            This unit is on the master's timeline. It has been cloned from
            the master, or is the master. Undefined with manual replication.

        postgresql.replication.manual

            Manual replication mode has been selected and the charm
            must not do any replication setup or maintenance.

        postgresql.replication.is_primary

            The unit is writable. It is either the master or manual
            replication mode is in effect.

        postgresql.replication.switchover

            In switchover to a new master. A switchover is a controlled
            failover, where the existing master is available.

        postgresql.replication.is_anointed

            In switchover and this unit is anointed to be the new master.
    """
    peers = helpers.get_peer_relation()
    reactive.toggle_state("postgresql.replication.has_peers", peers)
    if peers:
        reactive.set_state("postgresql.replication.had_peers")

    reactive.toggle_state("postgresql.replication.manual",
                          hookenv.config()["manual_replication"])

    master = get_master()  # None if postgresql.replication.manual state.
    reactive.toggle_state("postgresql.replication.is_master",
                          master == hookenv.local_unit())
    reactive.toggle_state("postgresql.replication.master.peered", peers
                          and master in peers)
    reactive.toggle_state(
        "postgresql.replication.master.authorized",
        peers and master in peers and authorized_by(master),
    )
    ready = reactive.is_state(
        "postgresql.replication.is_master") or reactive.is_state(
            "postgresql.replication.master.authorized")
    reactive.toggle_state("postgresql.replication.has_master", ready)

    anointed = get_anointed()
    reactive.toggle_state("postgresql.replication.switchover",
                          anointed is not None and anointed != master)
    reactive.toggle_state(
        "postgresql.replication.is_anointed",
        anointed is not None and anointed != master
        and anointed == hookenv.local_unit(),
    )

    reactive.toggle_state("postgresql.replication.is_primary",
                          postgresql.is_primary())

    if reactive.is_state("postgresql.replication.is_primary"):
        if reactive.is_state("postgresql.replication.is_master"):
            # If the unit is a primary and the master, it is on the master
            # timeline by definition and gets the 'cloned' state.
            reactive.set_state("postgresql.replication.cloned")
        elif reactive.is_state("postgresql.replication.is_anointed"):
            # The anointed unit retains its 'cloned' state.
            pass
        else:
            # If the unit is a primary and not the master, it is on a
            # divered timeline and needs to lose the 'cloned' state.
            reactive.remove_state("postgresql.replication.cloned")

    cloned = reactive.is_state("postgresql.replication.cloned")
    reactive.toggle_state(
        "postgresql.replication.failover",
        master != hookenv.local_unit() and peers and cloned
        and (master not in peers),
    )