blob: ea815a92806cae2dbd4342a56922af844a94041e [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* Functions to support administrative tasks with GPDB segments.
*
*/
#include "postgres.h"
#include "miscadmin.h"
#include "catalog/catquery.h"
#include "catalog/gp_segment_config.h"
#include "catalog/pg_database.h"
#include "catalog/pg_filespace.h"
#include "catalog/pg_filespace_entry.h"
#include "catalog/pg_proc.h"
#include "cdb/cdbresynchronizechangetracking.h"
#include "cdb/cdbdatabaseinfo.h"
#include "cdb/cdbdisp.h"
#include "cdb/cdbmirroredfilesysobj.h"
#include "cdb/cdbpersistentdatabase.h"
#include "cdb/cdbpersistentfilespace.h"
#include "cdb/cdbpersistentrelfile.h"
#include "cdb/cdbpersistenttablespace.h"
#include "cdb/cdbutil.h"
#include "cdb/cdbvars.h"
#include "commands/filespace.h"
#include "commands/tablespace.h"
#include "executor/spi.h"
#include "lib/stringinfo.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/segadmin.h"
#define MASTER_ONLY 0x1
#define UTILITY_MODE 0x2
#define SUPERUSER 0x4
#define READ_ONLY 0x8
#define SEGMENT_ONLY 0x10
#define STANDBY_ONLY 0x20
#define SINGLE_USER_MODE 0x40
/*
* Tell the caller whether a standby master is defined in the system.
*/
static bool
standby_exists()
{
return caql_getcount(
NULL,
cql("SELECT COUNT(*) FROM gp_segment_configuration "
" WHERE role = :1 ",
CharGetDatum(SEGMENT_ROLE_STANDBY_CONFIG))) > 0;
}
/*
* Check that the code is being called in right context.
*/
static void
mirroring_sanity_check(int flags, const char *func)
{
if ((flags & MASTER_ONLY) == MASTER_ONLY)
{
/* TODO: Add new check */
}
if ((flags & UTILITY_MODE) == UTILITY_MODE)
{
if (Gp_role != GP_ROLE_UTILITY)
elog(ERROR, "%s must be run in utility mode", func);
}
if ((flags & SINGLE_USER_MODE) == SINGLE_USER_MODE)
{
if (IsUnderPostmaster)
elog(ERROR, "%s must be run in single-user mode", func);
}
if ((flags & SUPERUSER) == SUPERUSER)
{
if (!superuser())
elog(ERROR, "%s can only be run by a superuser", func);
}
if ((flags & READ_ONLY) == READ_ONLY)
{
if (gp_set_read_only != true)
elog(ERROR, "%s can only be run if the system is in read only mode",
func);
}
if ((flags & SEGMENT_ONLY) == SEGMENT_ONLY)
{
/* TODO: Add new check */
}
if ((flags & STANDBY_ONLY) == STANDBY_ONLY)
{
/* TODO: Add new check */
}
}
/*
* Tell the master about a new segment.
*
* gp_add_segment(hostname, address, port,
* filespace_map)
*
* Args:
* hostname - host name string
* address - either hostname or something else
* port - port number
* filespace_map - A 2-d mapping of filespace to path on the new node
*
* Returns the dbid of the new segment.
*/
Datum
gp_add_segment(PG_FUNCTION_ARGS)
{
elog(ERROR, "gp_add_segment is not supported");
PG_RETURN_BOOL(false);
}
/*
* Master function to remove a segment from all catalogs
*/
static void
remove_segment(int16 order)
{
cqContext cqc;
int numDel;
if(order == MASTER_ORDER_ID || order == STANDBY_ORDER_ID)
return;
/* remove this segment from gp_segment_configuration */
Relation rel = heap_open(GpSegmentConfigRelationId, AccessExclusiveLock);
numDel= caql_getcount(caql_addrel(cqclr(&cqc), rel),
cql("DELETE FROM gp_segment_configuration "
" WHERE registration_order = :1",
Int16GetDatum(order)));
elog(LOG, "Remove segment successfully! Count : %d. Registration order : %d",
numDel,
order);
heap_close(rel, NoLock);
}
/*
* Remove knowledge of a segment from the master.
*
* gp_remove_segment(order)
*
* Args:
* order - order of registration
*
* Returns:
* true on success, otherwise error.
*/
Datum
gp_remove_segment(PG_FUNCTION_ARGS)
{
int16 order;
if (PG_ARGISNULL(0))
elog(ERROR, "Registration id cannot be NULL");
order = PG_GETARG_INT16(0);
mirroring_sanity_check(MASTER_ONLY | SUPERUSER | UTILITY_MODE,
"gp_remove_segment");
if (order == MASTER_ORDER_ID || order == STANDBY_ORDER_ID)
elog(ERROR, "Cannot remove master or standby");
remove_segment(order);
PG_RETURN_BOOL(true);
}
/*
* Add a mirror of an existing segment.
*
* gp_add_segment_mirror(contentid, hostname, address, port,
* replication_port, filespace_map)
*/
Datum
gp_add_segment_mirror(PG_FUNCTION_ARGS)
{
elog(ERROR, "gp_add_segment_mirror is not supported");
PG_RETURN_BOOL(false);
}
/*
* Remove a segment mirror.
*
* gp_remove_segment_mirror(contentid)
*
* Args:
* contentid - segment index at which to remove the mirror
*
* Returns:
* true upon success, otherwise throws error.
*/
Datum
gp_remove_segment_mirror(PG_FUNCTION_ARGS)
{
elog(ERROR, "gp_remove_segment_mirror is not supported");
PG_RETURN_BOOL(false);
}
/*
* Add a master standby.
*
* gp_add_master_standby(hostname, address)
*
* Args:
* hostname - as above
* address - as above
*
* Returns:
* dbid of the new standby
*/
Datum
gp_add_master_standby(PG_FUNCTION_ARGS)
{
CdbComponentDatabaseInfo *master = NULL;
Relation gprel;
Datum values[Natts_gp_segment_configuration];
bool nulls[Natts_gp_segment_configuration];
HeapTuple tuple;
cqContext cqc;
cqContext *pcqCtx = NULL;
if (PG_ARGISNULL(0))
elog(ERROR, "host name cannot be NULL");
if (PG_ARGISNULL(1))
elog(ERROR, "address cannot be NULL");
mirroring_sanity_check(MASTER_ONLY | UTILITY_MODE,
"gp_add_master_standby");
if (standby_exists())
elog(ERROR, "only a single master standby may be defined");
/* master */
master = registration_order_get_dbinfo(MASTER_ORDER_ID);
/* Lock exclusively to avoid concurrent changes */
gprel = heap_open(GpSegmentConfigRelationId, AccessExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), gprel),
cql("INSERT INTO gp_segment_configuration ", NULL));
MemSet(nulls, false, sizeof(nulls));
values[Anum_gp_segment_configuration_registration_order - 1] = Int32GetDatum(STANDBY_ORDER_ID);
values[Anum_gp_segment_configuration_role - 1] = CharGetDatum(SEGMENT_ROLE_STANDBY_CONFIG);
values[Anum_gp_segment_configuration_status - 1] = CharGetDatum('u');
values[Anum_gp_segment_configuration_port - 1] = Int32GetDatum(master->port);
values[Anum_gp_segment_configuration_hostname - 1] = PG_GETARG_DATUM(0);
values[Anum_gp_segment_configuration_address - 1] = PG_GETARG_DATUM(1);
nulls[Anum_gp_segment_configuration_description - 1] = true;
tuple = caql_form_tuple(pcqCtx, values, nulls);
/* insert a new tuple */
caql_insert(pcqCtx, tuple); /* implicit update of index as well */
caql_endscan(pcqCtx);
if(master)
pfree(master);
heap_close(gprel, NoLock);
PG_RETURN_INT16(1);
}
static void
update_gp_master_mirroring(char *str)
{
volatile bool connected = false;
volatile bool resetModsDML = false;
PG_TRY();
{
StringInfoData sql;
initStringInfo(&sql);
appendStringInfo(&sql, "update gp_master_mirroring set "
"summary_state = '%s', detail_state = null,"
"log_time = current_timestamp, error_message = null",
str);
if (SPI_OK_CONNECT != SPI_connect())
elog(ERROR, "cannot connect via SPI");
connected = true;
if (!allowSystemTableModsDML)
{
allowSystemTableModsDML = true;
resetModsDML = true;
}
if (SPI_execute(sql.data, false, 0) < 0)
elog(ERROR, "cannot update gp_master_mirroring");
if (resetModsDML)
allowSystemTableModsDML = false;
}
PG_CATCH();
{
if (connected)
SPI_finish();
if (resetModsDML)
allowSystemTableModsDML = false;
PG_RE_THROW();
}
PG_END_TRY();
SPI_finish();
}
/*
* Remove the master standby.
*
* gp_remove_master_standby()
*
* Returns:
* true upon success otherwise false
*/
Datum
gp_remove_master_standby(PG_FUNCTION_ARGS)
{
int numDel;
cqContext cqc;
mirroring_sanity_check(SUPERUSER | MASTER_ONLY | UTILITY_MODE,
"gp_remove_master_standby");
if (!standby_exists())
elog(ERROR, "no master standby defined");
Relation rel = heap_open(GpSegmentConfigRelationId, AccessExclusiveLock);
numDel= caql_getcount(caql_addrel(cqclr(&cqc), rel),
cql("DELETE FROM gp_segment_configuration "
" WHERE role = :1",
CharGetDatum(SEGMENT_ROLE_STANDBY_CONFIG)));
elog(LOG, "Remove standby, count : %d.", numDel);
heap_close(rel, NoLock);
update_gp_master_mirroring("Not Configured");
PG_RETURN_BOOL(true);
}
/*
* Run only on a segment, we use this to update the filespace locations for the
* PRIMARY as part of building a full segment. Used by gpexpand.
*
* gp_prep_new_segment(filespace_map)
*
* Args:
* filespace_map - as above
*
* Returns:
* true upon success, otherwise throws error
*
* XXX: This is not effective in HAWQ.
*/
Datum
gp_prep_new_segment(PG_FUNCTION_ARGS)
{
elog(ERROR, "gp_prep_new_segment is not supported");
PG_RETURN_BOOL(false);
}
/*
* Activate a standby. To do this, we need to change
*
* 1. Check that we're actually the standby
* 2. Remove standby from gp_segment_configuration.
*
* gp_activate_standby()
*
* Returns:
* true upon success, otherwise throws error.
*/
Datum
gp_activate_standby(PG_FUNCTION_ARGS)
{
cqContext cqc;
int numDel;
mirroring_sanity_check(SUPERUSER | UTILITY_MODE | STANDBY_ONLY,
PG_FUNCNAME_MACRO);
if (!AmIStandby())
elog(ERROR, "%s must be run on the standby master",
PG_FUNCNAME_MACRO);
/* remove standby from gp_segment_configuration */
Relation rel = heap_open(GpSegmentConfigRelationId, AccessExclusiveLock);
numDel= caql_getcount(caql_addrel(cqclr(&cqc), rel),
cql("DELETE FROM gp_segment_configuration "
" WHERE role = :1",
CharGetDatum(SEGMENT_ROLE_STANDBY_CONFIG)));
elog(LOG, "Remove standby while activating it, count : %d.", numDel);
heap_close(rel, NoLock);
/* done */
PG_RETURN_BOOL(true);
}
/*
* Add entries to persistent tables on a segment.
*
* gp_add_segment_persistent_entries(dbid, mirdbid, filespace_map)
*
* Args:
* dbid of the primary
* mirdbid - dbid of the mirror
* filespace_map - as above
*
* Returns:
* true upon success otherwise false
*
* Runs only at the segment level.
*/
Datum
gp_add_segment_persistent_entries(PG_FUNCTION_ARGS)
{
elog(ERROR, "gp_add_segment_persistent_entries is not supported");
PG_RETURN_BOOL(false);
}
/*
* Opposite of gp_add_segment_persistent_entries()
*/
Datum
gp_remove_segment_persistent_entries(PG_FUNCTION_ARGS)
{
elog(ERROR, "not support");
PG_RETURN_BOOL(false);
}
void update_segment_status_by_id(uint32_t id, char status)
{
/* we use AccessExclusiveLock to prevent races */
Relation rel = heap_open(GpSegmentConfigRelationId, AccessExclusiveLock);
HeapTuple tuple;
cqContext cqc;
cqContext *pcqCtx;
Assert(status == 'u' || status == 'd');
pcqCtx = caql_beginscan(caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM gp_segment_configuration "
" WHERE registration_order = :1 "
" FOR UPDATE ",
Int32GetDatum(id)));
tuple = caql_getnext(pcqCtx);
if (tuple != NULL) {
if (((Form_gp_segment_configuration)GETSTRUCT(tuple))->status != status) {
((Form_gp_segment_configuration)GETSTRUCT(tuple))->status = status;
caql_update_current(pcqCtx, tuple);
}
} else {
elog(LOG, "Can not find segment id: %d when update its status", id);
}
caql_endscan(pcqCtx);
heap_close(rel, NoLock);
}