| /*------------------------------------------------------------------------- |
| * |
| * 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); |
| } |