1 /* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
2    This program is free software; you can redistribute it and/or modify
3    it under the terms of the GNU General Public License, version 2.0,
4    as published by the Free Software Foundation.
5 
6    This program is also distributed with certain software (including
7    but not limited to OpenSSL) that is licensed under separate terms,
8    as designated in a particular file or component or in included license
9    documentation.  The authors of MySQL hereby grant you an additional
10    permission to link the program and your derivative works with the
11    separately licensed software that they have included with MySQL.
12 
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License, version 2.0, for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
21 
22 #include "sql/resourcegroups/resource_group_sql_cmd.h"
23 
24 #include <stdint.h>
25 #include <string.h>
26 #include <memory>
27 #include <new>
28 #include <string>
29 #include <utility>
30 #include <vector>
31 
32 #include "m_ctype.h"
33 #include "m_string.h"
34 #include "my_compiler.h"
35 #include "my_dbug.h"
36 #include "my_psi_config.h"
37 #include "my_sys.h"
38 #include "mysql/components/services/log_shared.h"
39 #include "mysql/components/services/psi_thread_bits.h"
40 #include "mysql/psi/mysql_mutex.h"
41 #include "mysql_com.h"
42 #include "mysqld_error.h"
43 #include "pfs_thread_provider.h"
44 #include "sql/auth/auth_acls.h"    // SUPER_ACL
45 #include "sql/auth/auth_common.h"  // check_readonly
46 #include "sql/auth/sql_security_ctx.h"
47 #include "sql/current_thd.h"                 // current_thd
48 #include "sql/dd/cache/dictionary_client.h"  // dd::cache::Dictionary_client
49 #include "sql/dd/dd_resource_group.h"        // resource_group_exists, etc.
50 #include "sql/dd/string_type.h"              // String_type
51 #include "sql/derror.h"                      // ER_THD
52 #include "sql/lock.h"                        // acquire_shared_global...
53 #include "sql/mdl.h"
54 #include "sql/mysqld_thd_manager.h"  // Find_thd_with_id
55 #include "sql/parse_tree_helpers.h"
56 #include "sql/resourcegroups/resource_group.h"           // Resource_group
57 #include "sql/resourcegroups/resource_group_mgr.h"       // Resource_group_mgr
58 #include "sql/resourcegroups/thread_resource_control.h"  // Thread_resource_control
59 #include "sql/sql_backup_lock.h"  // acquire_shared_backup_lock
60 #include "sql/sql_class.h"        // THD
61 #include "sql/sql_error.h"
62 #include "sql/sql_lex.h"  // is_invalid_string
63 #include "sql/system_variables.h"
64 #include "sql/thd_raii.h"
65 
66 namespace dd {
67 class Resource_group;
68 }  // namespace dd
69 
70 namespace {
71 /**
72   Acquire an exclusive MDL lock on resource group name.
73 
74   @param  thd  Pointer to THD context.
75   @param  res_grp_name  Resource group name.
76 
77   @return true if lock acquisition failed else false.
78 */
79 
acquire_exclusive_mdl_for_resource_group(THD * thd,const char * res_grp_name)80 static bool acquire_exclusive_mdl_for_resource_group(THD *thd,
81                                                      const char *res_grp_name) {
82   DBUG_TRACE;
83 
84   MDL_key mdl_key;
85   dd::Resource_group::create_mdl_key(res_grp_name, &mdl_key);
86 
87   MDL_request mdl_request;
88   MDL_REQUEST_INIT_BY_KEY(&mdl_request, &mdl_key, MDL_EXCLUSIVE,
89                           MDL_TRANSACTION);
90   if (thd->mdl_context.acquire_lock(&mdl_request,
91                                     thd->variables.lock_wait_timeout))
92     return true;
93 
94   return false;
95 }
96 
97 /**
98   Validate CPU id ranges provided by a user in the statements
99   CREATE RESOURCE GROUP, ALTER RESOURCE GROUP
100 
101   @param[out] vcpu_range_vector  vector of validated resourcegroups::Range
102                                  objects.
103   @param      cpu_list           Array of resourcegroups::Range objects
104                                  representing CPU identifiers or ranges of
105                                  CPU identifiers specified in the statements
106                                  CREATE RESOURCE GROUP, ALTER RESOURCE GROUP.
107   @param      num_vcpus          Number of VCPUS.
108 */
109 
validate_vcpu_range_vector(std::vector<resourcegroups::Range> * vcpu_range_vector,const Mem_root_array<resourcegroups::Range> * cpu_list,uint32_t num_vcpus)110 bool validate_vcpu_range_vector(
111     std::vector<resourcegroups::Range> *vcpu_range_vector,
112     const Mem_root_array<resourcegroups::Range> *cpu_list, uint32_t num_vcpus) {
113   DBUG_TRACE;
114   for (auto vcpu_range : *cpu_list) {
115     if (vcpu_range.m_start > vcpu_range.m_end) {
116       my_error(ER_INVALID_VCPU_RANGE, MYF(0), vcpu_range.m_start,
117                vcpu_range.m_end);
118       return true;
119     }
120 
121     if (vcpu_range.m_start >= num_vcpus || vcpu_range.m_end >= num_vcpus) {
122       my_error(ER_INVALID_VCPU_ID, MYF(0),
123                vcpu_range.m_start >= num_vcpus ? vcpu_range.m_start
124                                                : vcpu_range.m_end);
125       return true;
126     }
127 
128     vcpu_range_vector->emplace_back(
129         resourcegroups::Range(vcpu_range.m_start, vcpu_range.m_end));
130   }
131   return false;
132 }
133 
134 /**
135   This class represents a functional call to move a thread specified by
136   pfs_thread_id to a resource group specified in class' constructor.
137 */
138 
139 class Move_thread_to_default_group {
140  public:
Move_thread_to_default_group(resourcegroups::Resource_group * resource_group)141   explicit Move_thread_to_default_group(
142       resourcegroups::Resource_group *resource_group)
143       : m_resource_group(resource_group) {}
144 
operator ()(ulonglong pfs_thread_id)145   void operator()(ulonglong pfs_thread_id) {
146     auto res_grp_mgr = resourcegroups::Resource_group_mgr::instance();
147     auto applied_res_grp =
148         m_resource_group->type() == resourcegroups::Type::SYSTEM_RESOURCE_GROUP
149             ? res_grp_mgr->sys_default_resource_group()
150             : res_grp_mgr->usr_default_resource_group();
151     PSI_thread_attrs pfs_thread_attr;
152 
153     memset(&pfs_thread_attr, 0, sizeof(pfs_thread_attr));
154     if (!res_grp_mgr->get_thread_attributes(&pfs_thread_attr, pfs_thread_id)) {
155       applied_res_grp->controller()->apply_control(
156           pfs_thread_attr.m_thread_os_id);
157       res_grp_mgr->set_res_grp_in_pfs(applied_res_grp->name().c_str(),
158                                       applied_res_grp->name().length(),
159                                       pfs_thread_id);
160       if (!pfs_thread_attr.m_system_thread) {
161         Find_thd_with_id find_thd_with_id(pfs_thread_attr.m_processlist_id);
162         THD *thd =
163             Global_THD_manager::get_instance()->find_thd(&find_thd_with_id);
164         if (thd != nullptr) {
165           thd->resource_group_ctx()->m_cur_resource_group = nullptr;
166           mysql_mutex_assert_owner(&thd->LOCK_thd_data);
167           mysql_mutex_unlock(&thd->LOCK_thd_data);
168         }
169       }
170     }
171   }
172 
173  private:
174   resourcegroups::Resource_group *m_resource_group;
175 };
176 
177 /**
178   Check if given resource group name is a default resource group.
179 */
180 
is_default_resource_group(const char * res_grp_name)181 inline bool is_default_resource_group(const char *res_grp_name) {
182   return my_strcasecmp(system_charset_info, "USR_default", res_grp_name) == 0 ||
183          my_strcasecmp(system_charset_info, "SYS_default", res_grp_name) == 0;
184 }
185 
186 }  // Anonymous namespace
187 
execute(THD * thd)188 bool resourcegroups::Sql_cmd_create_resource_group::execute(THD *thd) {
189   DBUG_TRACE;
190 
191   if (check_readonly(thd, true)) return true;
192 
193   Security_context *sctx = thd->security_context();
194   if (!sctx->has_global_grant(STRING_WITH_LEN("RESOURCE_GROUP_ADMIN")).first) {
195     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RESOURCE_GROUP_ADMIN");
196     return true;
197   }
198 
199   // Resource group name validation.
200   if (is_invalid_string(m_name, system_charset_info)) return true;
201 
202   // VCPU IDs list validation.
203   uint32_t num_vcpus =
204       resourcegroups::Resource_group_mgr::instance()->num_vcpus();
205   DBUG_PRINT("info", ("Number of VCPUS: %u", num_vcpus));
206 
207   auto vcpu_range_vector = std::unique_ptr<std::vector<Range>>(
208       new (std::nothrow) std::vector<Range>);
209   if (vcpu_range_vector == nullptr) {
210     my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 0);
211     return true;
212   }
213 
214   if (validate_vcpu_range_vector(vcpu_range_vector.get(), m_cpu_list,
215                                  num_vcpus))
216     return true;
217 
218   if (acquire_shared_global_read_lock(thd, thd->variables.lock_wait_timeout) ||
219       acquire_shared_backup_lock(thd, thd->variables.lock_wait_timeout))
220     return true;
221 
222   // Acquire exclusive lock on the resource group name.
223   if (acquire_exclusive_mdl_for_resource_group(thd, m_name.str)) return true;
224 
225   auto res_grp_mgr = Resource_group_mgr::instance();
226   // Check whether resource group exists in-memory.
227   if (res_grp_mgr->get_resource_group(m_name.str) != nullptr) {
228     my_error(ER_RESOURCE_GROUP_EXISTS, MYF(0), m_name.str);
229     return true;
230   }
231 
232   bool resource_group_exists;
233   // Check the disk also for existence of resource group.
234   if (dd::resource_group_exists(thd->dd_client(), dd::String_type(m_name.str),
235                                 &resource_group_exists))
236     return true;
237 
238   if (resource_group_exists) {
239     my_error(ER_RESOURCE_GROUP_EXISTS, MYF(0), m_name.str);
240     return true;
241   }
242 
243   auto resource_group_ptr = res_grp_mgr->create_and_add_in_resource_group_hash(
244       m_name, m_type, m_enabled, std::move(vcpu_range_vector), m_priority);
245 
246   if (resource_group_ptr == nullptr) return true;
247 
248   Disable_autocommit_guard autocommit_guard(thd);
249   if (dd::create_resource_group(thd, *resource_group_ptr)) {
250     Resource_group_mgr::instance()->remove_resource_group(
251         std::string(m_name.str));
252     return true;
253   }
254 
255   my_ok(thd);
256   return false;
257 }
258 
259 /**
260   Check if a resource group specified by a name is present in memory.
261   Load a resource group from the Data Dictionary if it missed in memory.
262 
263   @param  thd  THD context.
264   @param  resource_group_name  Resource group name.
265 
266   @return nullptr in case of error, else return a pointer to an instance of
267   class resourcegroups::Resource_group.
268 
269   @note in case nullptr is returned an error is set in Diagnostics_area.
270 */
271 
check_and_load_resource_group(THD * thd,const LEX_CSTRING & resource_group_name)272 static inline resourcegroups::Resource_group *check_and_load_resource_group(
273     THD *thd, const LEX_CSTRING &resource_group_name) {
274   resourcegroups::Resource_group *resource_group =
275       resourcegroups::Resource_group_mgr::instance()->get_resource_group(
276           resource_group_name.str);
277 
278   if (resource_group == nullptr) {
279     // Check if resource group exists on-disk.
280     bool exists;
281     if (dd::resource_group_exists(thd->dd_client(),
282                                   dd::String_type(resource_group_name.str),
283                                   &exists))
284       // Error is reported by the dictionary subsystem.
285       return nullptr;
286 
287     if (!exists) {
288       my_error(ER_RESOURCE_GROUP_NOT_EXISTS, MYF(0), resource_group_name.str);
289       return nullptr;
290     }
291 
292     const dd::Resource_group *dd_resource_group = nullptr;
293     dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
294     if (thd->dd_client()->acquire(dd::String_type(resource_group_name.str),
295                                   &dd_resource_group))
296       // Error is reported by the dictionary subsystem.
297       return nullptr;
298 
299     resource_group = resourcegroups::Resource_group_mgr::instance()
300                          ->deserialize_resource_group(dd_resource_group);
301     if (resource_group == nullptr) {
302       my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 0);
303       return nullptr;
304     }
305   }
306 
307   return resource_group;
308 }
309 
execute(THD * thd)310 bool resourcegroups::Sql_cmd_alter_resource_group::execute(THD *thd) {
311   DBUG_TRACE;
312 
313   if (check_readonly(thd, true)) return true;
314 
315   Security_context *sctx = thd->security_context();
316   if (!sctx->has_global_grant(STRING_WITH_LEN("RESOURCE_GROUP_ADMIN")).first) {
317     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RESOURCE_GROUP_ADMIN");
318     return true;
319   }
320 
321   // Resource group name validation.
322   if (is_invalid_string(m_name, system_charset_info)) return true;
323 
324   // Disallow altering USR_default & SYS_default resource group.
325   if (is_default_resource_group(m_name.str)) {
326     my_error(ER_DISALLOWED_OPERATION, MYF(0), "Alter",
327              "default resource groups.");
328     return true;
329   }
330 
331   if (acquire_shared_global_read_lock(thd, thd->variables.lock_wait_timeout) ||
332       acquire_shared_backup_lock(thd, thd->variables.lock_wait_timeout))
333     return true;
334 
335   // Acquire exclusive lock on the resource group name.
336   if (acquire_exclusive_mdl_for_resource_group(thd, m_name.str)) return true;
337 
338   auto resource_group = check_and_load_resource_group(thd, m_name);
339 
340   if (resource_group == nullptr) return true;
341 
342   // VCPU IDs list validation.
343   uint32_t num_vcpus = Resource_group_mgr::instance()->num_vcpus();
344   DBUG_PRINT("info", ("Number of VCPUS: %u", num_vcpus));
345 
346   auto vcpu_range_vector = std::unique_ptr<std::vector<Range>>(
347       new (std::nothrow) std::vector<Range>);
348   if (vcpu_range_vector == nullptr) {
349     my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 0);
350     return true;
351   }
352 
353   if (validate_vcpu_range_vector(vcpu_range_vector.get(), m_cpu_list,
354                                  num_vcpus))
355     return true;
356 
357   if (validate_resource_group_priority(thd, &m_priority, m_name,
358                                        resource_group->type()))
359     return true;
360 
361   // FORCE option is not paired with DISABLE option.
362   if (m_force && (!m_use_enable || m_enable)) {
363     my_error(ER_INVALID_USE_OF_FORCE_OPTION, MYF(0));
364     return true;
365   }
366 
367   Thread_resource_control *thr_res_ctrl = resource_group->controller();
368   bool thr_res_ctrl_change = false;
369   if (m_priority != thr_res_ctrl->priority()) {
370     thr_res_ctrl->set_priority(m_priority);
371     thr_res_ctrl_change = true;
372   }
373 
374   if (!vcpu_range_vector->empty()) {
375     thr_res_ctrl->set_vcpu_vector(*vcpu_range_vector);
376     thr_res_ctrl_change = true;
377   }
378 
379   if (m_use_enable && m_enable != resource_group->enabled()) {
380     resource_group->set_enabled(m_enable);
381     thr_res_ctrl_change = m_enable;
382   } else
383     thr_res_ctrl_change = resource_group->enabled();
384 
385   // Update on-disk resource group.
386   Disable_autocommit_guard autocommit_guard(thd);
387   dd::String_type name(m_name.str);
388   if (update_resource_group(thd, name, *resource_group)) return true;
389 
390   /*
391     Reapply controls on threads if there was some change in
392     the thread resource controls and the resource group is enabled.
393   */
394   if (thr_res_ctrl_change) {
395     resource_group->apply_control_func(
396         [resource_group](ulonglong pfs_thread_id) {
397           auto res_grp_mgr_ptr = Resource_group_mgr::instance();
398           PSI_thread_attrs pfs_thread_attr;
399           memset(&pfs_thread_attr, 0, sizeof(pfs_thread_attr));
400           if (!res_grp_mgr_ptr->get_thread_attributes(&pfs_thread_attr,
401                                                       pfs_thread_id))
402             resource_group->controller()->apply_control(
403                 pfs_thread_attr.m_thread_os_id);
404         });
405   }
406 
407   /*
408     If some threads are bound with resource group, then
409     (i) If FORCE option is specified, move the threads bound with this
410         resource group to respective default resource groups.
411     (ii) If FORCE option is not specified, the resource group is just
412          disabled.
413   */
414   if (resource_group->is_bound_to_threads()) {
415     if (m_force) {
416       // Move all threads associated with this to default resource groups.
417       resource_group->apply_control_func(
418           Move_thread_to_default_group(resource_group));
419       resource_group->clear();
420     }
421   }
422   my_ok(thd);
423   return false;
424 }
425 
execute(THD * thd)426 bool resourcegroups::Sql_cmd_drop_resource_group::execute(THD *thd) {
427   DBUG_TRACE;
428 
429   if (check_readonly(thd, true)) return true;
430 
431   Security_context *sctx = thd->security_context();
432   if (!sctx->has_global_grant(STRING_WITH_LEN("RESOURCE_GROUP_ADMIN")).first) {
433     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RESOURCE_GROUP_ADMIN");
434     return true;
435   }
436 
437   // Resource group name validation.
438   if (is_invalid_string(m_name, system_charset_info)) return true;
439 
440   // Disallow dropping USR_default & SYS_default resource group.
441   if (is_default_resource_group(m_name.str)) {
442     my_error(ER_DISALLOWED_OPERATION, MYF(0), "Drop operation ",
443              "default resource groups.");
444     return true;
445   }
446 
447   if (acquire_shared_global_read_lock(thd, thd->variables.lock_wait_timeout) ||
448       acquire_shared_backup_lock(thd, thd->variables.lock_wait_timeout))
449     return true;
450 
451   // Acquire exclusive lock on the resource group name.
452   if (acquire_exclusive_mdl_for_resource_group(thd, m_name.str)) return true;
453 
454   auto resource_group = check_and_load_resource_group(thd, m_name);
455 
456   if (resource_group == nullptr) return true;
457 
458   if (resource_group->is_bound_to_threads()) {
459     if (m_force)  // move all threads to the default resource group.
460     {
461       resource_group->apply_control_func(
462           Move_thread_to_default_group(resource_group));
463       resource_group->clear();
464     } else {
465       my_error(ER_RESOURCE_GROUP_BUSY, MYF(0), m_name.str);
466       return true;
467     }
468   }
469 
470   // Remove from on-disk resource group.
471   Disable_autocommit_guard autocommit_guard(thd);
472   if (dd::drop_resource_group(thd, m_name.str)) return true;
473 
474   // Remove from in-memory hash the resource group.
475   if (resource_group != nullptr)
476     Resource_group_mgr::instance()->remove_resource_group(m_name.str);
477 
478   my_ok(thd);
479   return false;
480 }
481 
482 /**
483   Check if resource group controls can be applied to thread
484   identified by PFS thread id. Apply the controls to thread
485   if the checks were successful.
486 
487   @param  thd              THD context
488   @param  thread_id        Thread id of the thread.
489   @param  resource_group   Pointer to resource group.
490   @param  error            Log error so that error is returned to client.
491 
492   @returns true if the function fails else false.
493 */
494 
check_and_apply_resource_grp(THD * thd,ulonglong thread_id,resourcegroups::Resource_group * resource_group,bool error)495 static inline bool check_and_apply_resource_grp(
496     THD *thd, ulonglong thread_id,
497     resourcegroups::Resource_group *resource_group, bool error) {
498   PSI_thread_attrs pfs_thread_attr;
499   memset(&pfs_thread_attr, 0, sizeof(pfs_thread_attr));
500   auto res_grp_mgr = resourcegroups::Resource_group_mgr::instance();
501 
502   if (res_grp_mgr->get_thread_attributes(&pfs_thread_attr, thread_id) ||
503       thread_id != pfs_thread_attr.m_thread_internal_id) {
504     if (error)
505       my_error(ER_INVALID_THREAD_ID, MYF(0), thread_id);
506     else
507       push_warning_printf(current_thd, Sql_condition::SL_WARNING,
508                           ER_INVALID_THREAD_ID,
509                           ER_THD(current_thd, ER_INVALID_THREAD_ID), thread_id);
510     return true;
511   }
512 
513   bool res_grp_match = pfs_thread_attr.m_system_thread
514                            ? (resource_group->type() ==
515                               resourcegroups::Type::SYSTEM_RESOURCE_GROUP)
516                            : (resource_group->type() ==
517                               resourcegroups::Type::USER_RESOURCE_GROUP);
518 
519   if (!res_grp_match) {
520     if (error)
521       my_error(ER_RESOURCE_GROUP_BIND_FAILED, MYF(0),
522                resource_group->name().c_str(), thread_id,
523                "Resource group type and thread type doesn't match.");
524     else
525       push_warning_printf(current_thd, Sql_condition::SL_WARNING,
526                           ER_RESOURCE_GROUP_BIND_FAILED,
527                           ER_THD(thd, ER_RESOURCE_GROUP_BIND_FAILED),
528                           resource_group->name().c_str(), thread_id,
529                           "Resource group type & thread type doesn't match");
530     return true;
531   }
532 
533   if (!res_grp_mgr->thread_priority_available() &&
534       resource_group->controller()->priority() != 0)
535     push_warning_printf(current_thd, Sql_condition::SL_WARNING,
536                         ER_ATTRIBUTE_IGNORED,
537                         ER_THD(current_thd, ER_ATTRIBUTE_IGNORED),
538                         "thread_priority", "using default value");
539 
540   // Check if resource group is already bound to this thread.
541   if (resource_group->is_pfs_thread_id_exists(thread_id)) return false;
542 
543   MDL_ticket *ticket = nullptr;
544   if (res_grp_mgr->acquire_shared_mdl_for_resource_group(
545           thd, pfs_thread_attr.m_groupname, MDL_EXPLICIT, &ticket, true)) {
546     if (error)
547       my_error(ER_RESOURCE_GROUP_BIND_FAILED, MYF(0),
548                resource_group->name().c_str(), thread_id,
549                "Unable to acquire  MDL lock.");
550     else {
551       thd->clear_error();
552       push_warning_printf(current_thd, Sql_condition::SL_WARNING,
553                           ER_RESOURCE_GROUP_BIND_FAILED,
554                           ER_THD(current_thd, ER_RESOURCE_GROUP_BIND_FAILED),
555                           resource_group->name().c_str(), thread_id,
556                           "Unable to acquire MDL lock.");
557     }
558     return true;
559   }
560 
561   resourcegroups::Resource_group *prev_cur_res_grp =
562       resourcegroups::Resource_group_mgr::instance()->get_resource_group(
563           std::string(pfs_thread_attr.m_groupname));
564 
565   if (resource_group->controller()->apply_control(
566           pfs_thread_attr.m_thread_os_id)) {
567     if (error)
568       my_error(ER_RESOURCE_GROUP_BIND_FAILED, MYF(0),
569                resource_group->name().c_str(), thread_id,
570                "Failed to apply thread controls.");
571     else
572       push_warning_printf(current_thd, Sql_condition::SL_WARNING,
573                           ER_RESOURCE_GROUP_BIND_FAILED,
574                           ER_THD(current_thd, ER_RESOURCE_GROUP_BIND_FAILED),
575                           resource_group->name().c_str(), thread_id,
576                           "Failed to apply thread controls.");
577     res_grp_mgr->release_shared_mdl_for_resource_group(thd, ticket);
578     return true;
579   }
580 
581   // Set resource group context for non-system threads.
582   if (!pfs_thread_attr.m_system_thread) {
583     Find_thd_with_id find_thd_with_id(pfs_thread_attr.m_processlist_id);
584     THD *cur_thd =
585         Global_THD_manager::get_instance()->find_thd(&find_thd_with_id);
586     if (cur_thd != nullptr) {
587       cur_thd->resource_group_ctx()->m_cur_resource_group = resource_group;
588       mysql_mutex_unlock(&cur_thd->LOCK_thd_data);
589     }
590   }
591 
592   if (prev_cur_res_grp != nullptr)
593     prev_cur_res_grp->remove_pfs_thread_id(thread_id);
594   resource_group->add_pfs_thread_id(thread_id);
595 
596   res_grp_mgr->set_res_grp_in_pfs(resource_group->name().c_str(),
597                                   resource_group->name().length(), thread_id);
598   res_grp_mgr->release_shared_mdl_for_resource_group(thd, ticket);
599   return false;
600 }
601 
602 /**
603   Check if user has sufficient privilege to exercise SET RESOURCE GROUP.
604 
605   @param sctx Pointer to security context.
606 
607   @return true if sufficient privilege exists for SET RESOURCE GROUP else false.
608 */
609 
check_resource_group_set_privilege(Security_context * sctx)610 static bool check_resource_group_set_privilege(Security_context *sctx) {
611   if (!(sctx->has_global_grant(STRING_WITH_LEN("RESOURCE_GROUP_ADMIN")).first ||
612         sctx->has_global_grant(STRING_WITH_LEN("RESOURCE_GROUP_USER")).first)) {
613     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
614              "RESOURCE_GROUP_ADMIN OR RESOURCE_GROUP_USER");
615     return true;
616   }
617   return false;
618 }
619 
execute(THD * thd)620 bool resourcegroups::Sql_cmd_set_resource_group::execute(THD *thd) {
621   DBUG_TRACE;
622 
623   Security_context *sctx = thd->security_context();
624   if (check_resource_group_set_privilege(sctx)) return true;
625 
626   // Acquire exclusive lock on the resource group name to synchronize with hint.
627   if (acquire_exclusive_mdl_for_resource_group(thd, m_name.str)) return true;
628 
629   auto resource_group = check_and_load_resource_group(thd, m_name);
630   if (resource_group == nullptr) return true;
631 
632   if ((resource_group->type() == Type::SYSTEM_RESOURCE_GROUP) &&
633       !sctx->has_global_grant(STRING_WITH_LEN("RESOURCE_GROUP_ADMIN")).first) {
634     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "RESOURCE_GROUP_ADMIN");
635     return true;
636   }
637 
638   if (!resource_group->enabled()) {
639     my_error(ER_RESOURCE_GROUP_DISABLED, MYF(0),
640              resource_group->name().c_str());
641     return true;
642   }
643 
644   if (m_thread_id_list == nullptr || m_thread_id_list->empty()) {
645     ulonglong pfs_thread_id = 0;
646 #ifdef HAVE_PSI_THREAD_INTERFACE
647     pfs_thread_id = PSI_THREAD_CALL(get_current_thread_internal_id)();
648 #endif
649 
650     if (resource_group->type() != Type::USER_RESOURCE_GROUP) {
651       my_error(ER_RESOURCE_GROUP_BIND_FAILED, MYF(0),
652                resource_group->name().c_str(), pfs_thread_id,
653                "System resource group can't be applied to user thread.");
654       return true;
655     }
656 
657     auto res_grp_mgr = resourcegroups::Resource_group_mgr::instance();
658     const char *resource_group_name = nullptr;
659     mysql_mutex_lock(&thd->LOCK_thd_data);
660     auto cur_res_grp = thd->resource_group_ctx()->m_cur_resource_group;
661     if (cur_res_grp != nullptr)
662       resource_group_name = cur_res_grp->name().c_str();
663     mysql_mutex_unlock(&thd->LOCK_thd_data);
664 
665     MDL_ticket *ticket = nullptr;
666     if (resource_group_name != nullptr &&
667         res_grp_mgr->acquire_shared_mdl_for_resource_group(
668             thd, resource_group_name, MDL_EXPLICIT, &ticket, true)) {
669       my_error(ER_RESOURCE_GROUP_BIND_FAILED, MYF(0),
670                resource_group->name().c_str(), pfs_thread_id,
671                "Unable to acquire MDL lock.");
672       return true;
673     }
674 
675     if (resource_group->controller()->apply_control()) {
676       my_error(ER_RESOURCE_GROUP_BIND_FAILED, MYF(0),
677                resource_group->name().c_str(), pfs_thread_id,
678                "Failed to apply thread resource controls");
679       return true;
680     }
681 
682     mysql_mutex_lock(&thd->LOCK_thd_data);
683     cur_res_grp = thd->resource_group_ctx()->m_cur_resource_group;
684     thd->resource_group_ctx()->m_cur_resource_group = resource_group;
685     mysql_mutex_unlock(&thd->LOCK_thd_data);
686 
687     if (cur_res_grp != nullptr)
688       cur_res_grp->remove_pfs_thread_id(pfs_thread_id);
689 
690     resourcegroups::Resource_group_mgr::instance()->set_res_grp_in_pfs(
691         resource_group->name().c_str(), resource_group->name().length(),
692         pfs_thread_id);
693     resource_group->add_pfs_thread_id(pfs_thread_id);
694     if (ticket != nullptr)
695       res_grp_mgr->release_shared_mdl_for_resource_group(thd, ticket);
696   } else {
697     if (m_thread_id_list->size() == 1 &&
698         check_and_apply_resource_grp(thd, m_thread_id_list->at(0),
699                                      resource_group, true))
700       return true;
701     else
702       for (const auto &thread_id : *m_thread_id_list)
703         (void)check_and_apply_resource_grp(thd, thread_id, resource_group,
704                                            false);
705   }
706 
707   my_ok(thd);
708   return false;
709 }
710 
prepare(THD * thd)711 bool resourcegroups::Sql_cmd_set_resource_group::prepare(THD *thd) {
712   DBUG_TRACE;
713 
714   if (Sql_cmd::prepare(thd)) return true;
715 
716   bool rc = check_resource_group_set_privilege(thd->security_context());
717 
718   return rc;
719 }
720