1 /*
2  * This file and its contents are licensed under the Timescale License.
3  * Please see the included NOTICE for copyright information and
4  * LICENSE-TIMESCALE for a copy of the license.
5  */
6 
7 #include <postgres.h>
8 #include <catalog/pg_type.h>
9 #include <catalog/pg_proc.h>
10 #include <catalog/pg_inherits.h>
11 #include <funcapi.h>
12 #include <utils/builtins.h>
13 #include <utils/lsyscache.h>
14 #include <utils/syscache.h>
15 #include <utils/array.h>
16 #include <miscadmin.h>
17 #include <access/xact.h>
18 #include <cache.h>
19 #include <nodes/pg_list.h>
20 
21 #include "dimension.h"
22 #include "errors.h"
23 #include "hypertable.h"
24 #include "utils.h"
25 #include "hypertable_cache.h"
26 #include "chunk.h"
27 #include "chunk_data_node.h"
28 
29 #include <foreign/foreign.h>
30 #include <libpq-fe.h>
31 
32 #include "fdw/fdw.h"
33 #include "data_node.h"
34 #include "deparse.h"
35 #include "remote/dist_commands.h"
36 #include "compat/compat.h"
37 #include "hypertable_data_node.h"
38 #include "extension.h"
39 
40 static List *
data_node_append(List * data_nodes,int32 hypertable_id,const char * node_name,int32 node_hypertable_id,bool block_chunks)41 data_node_append(List *data_nodes, int32 hypertable_id, const char *node_name,
42 				 int32 node_hypertable_id, bool block_chunks)
43 {
44 	ForeignServer *server = data_node_get_foreign_server(node_name, ACL_NO_CHECK, true, false);
45 	HypertableDataNode *hdn = palloc0(sizeof(HypertableDataNode));
46 
47 	hdn->fd.hypertable_id = hypertable_id;
48 	namestrcpy(&hdn->fd.node_name, node_name);
49 	hdn->fd.node_hypertable_id = node_hypertable_id;
50 	hdn->foreign_server_oid = server->serverid;
51 	hdn->fd.block_chunks = block_chunks;
52 
53 	return lappend(data_nodes, hdn);
54 }
55 
56 /*  Returns the remote hypertable ids for the data_nodes (in the same order)
57  */
58 static List *
hypertable_create_data_node_tables(int32 hypertable_id,List * data_nodes)59 hypertable_create_data_node_tables(int32 hypertable_id, List *data_nodes)
60 {
61 	Hypertable *ht = ts_hypertable_get_by_id(hypertable_id);
62 	ListCell *cell;
63 	List *remote_ids = NIL;
64 	DistCmdResult *dist_res;
65 	DeparsedHypertableCommands *commands = deparse_get_distributed_hypertable_create_command(ht);
66 
67 	foreach (cell, deparse_get_tabledef_commands(ht->main_table_relid))
68 		ts_dist_cmd_run_on_data_nodes(lfirst(cell), data_nodes, true);
69 
70 	dist_res = ts_dist_cmd_invoke_on_data_nodes(commands->table_create_command, data_nodes, true);
71 	foreach (cell, data_nodes)
72 	{
73 		PGresult *res = ts_dist_cmd_get_result_by_node_name(dist_res, lfirst(cell));
74 
75 		Assert(PQntuples(res) == 1);
76 		Assert(PQnfields(res) == AttrNumberGetAttrOffset(_Anum_create_hypertable_max));
77 		remote_ids =
78 			lappend(remote_ids,
79 					(void *) Int32GetDatum(atoi(
80 						PQgetvalue(res, 0, AttrNumberGetAttrOffset(Anum_create_hypertable_id)))));
81 	}
82 	ts_dist_cmd_close_response(dist_res);
83 
84 	foreach (cell, commands->dimension_add_commands)
85 		ts_dist_cmd_run_on_data_nodes(lfirst(cell), data_nodes, true);
86 
87 	foreach (cell, commands->grant_commands)
88 		ts_dist_cmd_run_on_data_nodes(lfirst(cell), data_nodes, true);
89 
90 	return remote_ids;
91 }
92 
93 /*
94  * Assign data nodes to a hypertable.
95  *
96  * Given a list of data node names, add mappings to ensure the
97  * hypertable is distributed across those nodes.
98  *
99  * Returns a list of HypertableDataNode objects that correspond to the given
100  * data node names.
101  */
102 List *
hypertable_assign_data_nodes(int32 hypertable_id,List * nodes)103 hypertable_assign_data_nodes(int32 hypertable_id, List *nodes)
104 {
105 	ListCell *lc;
106 	List *assigned_nodes = NIL;
107 	List *remote_ids = hypertable_create_data_node_tables(hypertable_id, nodes);
108 	ListCell *id_cell;
109 
110 	Assert(nodes->length == remote_ids->length);
111 	forboth (lc, nodes, id_cell, remote_ids)
112 	{
113 		assigned_nodes =
114 			data_node_append(assigned_nodes, hypertable_id, lfirst(lc), lfirst_int(id_cell), false);
115 	}
116 
117 	ts_hypertable_data_node_insert_multi(assigned_nodes);
118 
119 	return assigned_nodes;
120 }
121 
122 /*
123  * Validate data nodes when creating a new hypertable.
124  *
125  * The function is passed the explicit array of data nodes given by the user,
126  * if any.
127  *
128  * If the data node array is NULL (no data nodes specified), we return all
129  * data nodes that the user is allowed to use.
130  *
131  */
132 List *
hypertable_get_and_validate_data_nodes(ArrayType * nodearr)133 hypertable_get_and_validate_data_nodes(ArrayType *nodearr)
134 {
135 	bool fail_on_aclcheck = nodearr != NULL;
136 	List *data_nodes = NIL;
137 	int num_data_nodes;
138 	List *all_data_nodes = NIL;
139 
140 	/* If the user explicitly specified a set of data nodes (data_node_arr is
141 	 * non-NULL), we validate the given array and fail if the user doesn't
142 	 * have USAGE on all of them. Otherwise, we get a list of all
143 	 * database-configured data nodes that the user has USAGE on. */
144 	data_nodes = data_node_get_filtered_node_name_list(nodearr, ACL_USAGE, fail_on_aclcheck);
145 	num_data_nodes = list_length(data_nodes);
146 
147 	if (NULL == nodearr)
148 	{
149 		/* No explicit set of data nodes given. Check if there are any data
150 		 * nodes that the user cannot use due to lack of permissions and
151 		 * raise a NOTICE if some of them cannot be used. */
152 		all_data_nodes = data_node_get_node_name_list();
153 		int num_nodes_not_used = list_length(all_data_nodes) - list_length(data_nodes);
154 
155 		if (num_nodes_not_used > 0)
156 			ereport(NOTICE,
157 					(errmsg("%d of %d data nodes not used by this hypertable due to lack of "
158 							"permissions",
159 							num_nodes_not_used,
160 							list_length(all_data_nodes)),
161 					 errhint("Grant USAGE on data nodes to attach them to a hypertable.")));
162 	}
163 
164 	/*
165 	 * In this case, if we couldn't find any valid data nodes to assign, it
166 	 * means that they do not have the right permissions on the data nodes or
167 	 * that there were no data nodes to assign. Depending on the case, we
168 	 * print different error details and hints to aid the user.
169 	 */
170 	if (num_data_nodes == 0)
171 		ereport(ERROR,
172 				(errcode(ERRCODE_TS_INSUFFICIENT_NUM_DATA_NODES),
173 				 errmsg("no data nodes can be assigned to the hypertable"),
174 				 errdetail(list_length(all_data_nodes) == 0 ?
175 							   "No data nodes where available to assign to the hypertable." :
176 							   "Data nodes exist, but none have USAGE privilege."),
177 				 errhint(list_length(all_data_nodes) == 0 ?
178 							 "Add data nodes to the database." :
179 							 "Grant USAGE on data nodes to attach them to the hypertable.")));
180 
181 	if (num_data_nodes == 1)
182 		ereport(WARNING,
183 				(errmsg("only one data node was assigned to the hypertable"),
184 				 errdetail("A distributed hypertable should have at least two data nodes for best "
185 						   "performance."),
186 				 errhint(
187 					 list_length(all_data_nodes) == 1 ?
188 						 "Add more data nodes to the database and attach them to the hypertable." :
189 						 "Grant USAGE on data nodes and attach them to the hypertable.")));
190 
191 	if (num_data_nodes > MAX_NUM_HYPERTABLE_DATA_NODES)
192 		ereport(ERROR,
193 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
194 				 errmsg("max number of data nodes exceeded"),
195 				 errhint("The number of data nodes cannot exceed %d.",
196 						 MAX_NUM_HYPERTABLE_DATA_NODES)));
197 
198 	return data_nodes;
199 }
200 
201 void
hypertable_make_distributed(Hypertable * ht,List * data_node_names)202 hypertable_make_distributed(Hypertable *ht, List *data_node_names)
203 {
204 	hypertable_assign_data_nodes(ht->fd.id, data_node_names);
205 }
206 
207 static bool
hypertable_is_underreplicated(Hypertable * const ht,const int16 replication_factor)208 hypertable_is_underreplicated(Hypertable *const ht, const int16 replication_factor)
209 {
210 	ListCell *lc;
211 	List *chunks = find_inheritance_children(ht->main_table_relid, NoLock);
212 
213 	Assert(hypertable_is_distributed(ht));
214 
215 	foreach (lc, chunks)
216 	{
217 		Oid chunk_oid = lfirst_oid(lc);
218 		Chunk *chunk = ts_chunk_get_by_relid(chunk_oid, true);
219 		List *replicas = ts_chunk_data_node_scan_by_chunk_id(chunk->fd.id, CurrentMemoryContext);
220 
221 		Assert(get_rel_relkind(chunk_oid) == RELKIND_FOREIGN_TABLE);
222 
223 		if (list_length(replicas) < replication_factor)
224 			return true;
225 	}
226 	return false;
227 }
228 
229 static void
update_replication_factor(Hypertable * const ht,const int32 replication_factor_in)230 update_replication_factor(Hypertable *const ht, const int32 replication_factor_in)
231 {
232 	const int16 replication_factor =
233 		ts_validate_replication_factor(replication_factor_in, false, true);
234 
235 	ht->fd.replication_factor = replication_factor;
236 	ts_hypertable_update(ht);
237 	if (list_length(ht->data_nodes) < replication_factor)
238 		ereport(ERROR,
239 				(errcode(ERRCODE_TS_INSUFFICIENT_NUM_DATA_NODES),
240 				 errmsg("replication factor too large for hypertable \"%s\"",
241 						NameStr(ht->fd.table_name)),
242 				 errdetail("The hypertable has %d data nodes attached, while "
243 						   "the replication factor is %d.",
244 						   list_length(ht->data_nodes),
245 						   replication_factor),
246 				 errhint("Decrease the replication factor or attach more data "
247 						 "nodes to the hypertable.")));
248 	if (hypertable_is_underreplicated(ht, replication_factor))
249 		ereport(WARNING,
250 				(errcode(ERRCODE_WARNING),
251 				 errmsg("hypertable \"%s\" is under-replicated", NameStr(ht->fd.table_name)),
252 				 errdetail("Some chunks have less than %d replicas.", replication_factor)));
253 }
254 
255 Datum
hypertable_set_replication_factor(PG_FUNCTION_ARGS)256 hypertable_set_replication_factor(PG_FUNCTION_ARGS)
257 {
258 	const Oid table_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
259 	const int32 replication_factor_in = PG_ARGISNULL(1) ? 0 : PG_GETARG_INT32(1);
260 	Cache *hcache;
261 	Hypertable *ht;
262 
263 	TS_PREVENT_FUNC_IF_READ_ONLY();
264 
265 	if (!OidIsValid(table_relid))
266 		ereport(ERROR,
267 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
268 				 errmsg("invalid hypertable: cannot be NULL")));
269 
270 	hcache = ts_hypertable_cache_pin();
271 	ht = ts_hypertable_cache_get_entry(hcache, table_relid, CACHE_FLAG_NONE);
272 
273 	if (!hypertable_is_distributed(ht))
274 		ereport(ERROR,
275 				(errcode(ERRCODE_TS_HYPERTABLE_NOT_DISTRIBUTED),
276 				 errmsg("hypertable \"%s\" is not distributed", get_rel_name(table_relid))));
277 
278 	update_replication_factor(ht, replication_factor_in);
279 
280 	ts_cache_release(hcache);
281 
282 	PG_RETURN_VOID();
283 }
284