codekingpro/portable-devtools
114k
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10"""Implements Resource Groups for PPAS 9.4 and above"""11 12import json13from functools import wraps14 15from pgadmin.browser.server_groups import servers16from flask import render_template, make_response, request, jsonify17from flask_babel import gettext18from pgadmin.browser.collection import CollectionNodeModule19from pgadmin.browser.utils import NodeView20from pgadmin.utils.ajax import make_json_response, \21 make_response as ajax_response, internal_server_error, gone22from pgadmin.utils.ajax import precondition_required23from pgadmin.utils.driver import get_driver24from config import PG_DEFAULT_DRIVER25from pgadmin.utils.constants import DATABASE_LAST_SYSTEM_OID26 27 28class ResourceGroupModule(CollectionNodeModule):29 """30 class ResourceGroupModule(CollectionNodeModule)31 32 A module class for Resource Group node derived from33 CollectionNodeModule.34 35 Methods:36 -------37 * __init__(*args, **kwargs)38 - Method is used to initialize the ResourceGroupModule and it's39 base module.40 41 * backend_supported(manager, **kwargs)42 - This function is used to check the database server type and version.43 Resource Group only supported in PPAS 9.4 and above.44 45 * get_nodes(gid, sid, did)46 - Method is used to generate the browser collection node.47 48 * node_inode()49 - Method is overridden from its base class to make the node as leaf node.50 51 * script_load()52 - Load the module script for resource group, when any of the server53 node is initialized.54 """55 56 _NODE_TYPE = 'resource_group'57 _COLLECTION_LABEL = gettext("Resource Groups")58 59 def __init__(self, *args, **kwargs):60 """61 Method is used to initialize the ResourceGroupModule and62 it's base module.63 64 Args:65 *args:66 **kwargs:67 """68 super().__init__(*args, **kwargs)69 70 self.min_ver = 9040071 self.max_ver = None72 self.server_type = ['ppas']73 74 def get_nodes(self, gid, sid):75 """76 Method is used to generate the browser collection node77 78 Args:79 gid: Server Group ID80 sid: Server ID81 """82 if self.has_nodes(83 sid, None,84 base_template_path=ResourceGroupView.BASE_TEMPLATE_PATH):85 yield self.generate_browser_collection_node(sid)86 87 @property88 def node_inode(self):89 """90 Override this property to make the node as leaf node.91 92 Returns: False as this is the leaf node93 """94 return False95 96 @property97 def script_load(self):98 """99 Load the module script for resource group, when any of the server100 node is initialized.101 102 Returns: node type of the server module.103 """104 return servers.ServerModule.NODE_TYPE105 106 @property107 def module_use_template_javascript(self):108 """109 Returns whether Jinja2 template is used for generating the javascript110 module.111 """112 return False113 114 115blueprint = ResourceGroupModule(__name__)116 117 118class ResourceGroupView(NodeView):119 """120 class ResourceGroupView(NodeView)121 122 A view class for resource group node derived from NodeView.123 This class is responsible for all the stuff related to view like124 create/update/delete resource group, showing properties of resource125 group node, showing sql in sql pane.126 127 Methods:128 -------129 * __init__(**kwargs)130 - Method is used to initialize the ResourceGroupView and it's base view.131 132 * check_precondition()133 - This function will behave as a decorator which will checks134 database connection before running view, it will also attaches135 manager,conn & template_path properties to self136 137 * list()138 - This function is used to list all the resource group nodes within139 that collection.140 141 * nodes()142 - This function will used to create all the child node within that143 collection. Here it will create all the resource group node.144 145 * properties(gid, sid, did, rg_id)146 - This function will show the properties of the selected resource147 group node148 149 * create(gid, sid, did, rg_id)150 - This function will create the new resource group object151 152 * update(gid, sid, did, rg_id)153 - This function will update the data for the selected resource group node154 155 * delete(self, gid, sid, rg_id):156 - This function will drop the resource group object157 158 * msql(gid, sid, did, rg_id)159 - This function is used to return modified SQL for the selected160 resource group node161 162 * get_sql(data, rg_id)163 - This function will generate sql from model data164 165 * sql(gid, sid, did, rg_id):166 - This function will generate sql to show it in sql pane for the167 selected resource group node.168 """169 170 node_type = blueprint.node_type171 BASE_TEMPLATE_PATH = 'resource_groups/sql/#{0}#'172 _PROPERTIES_SQL = 'properties.sql'173 _CREATE_SQL = 'create.sql'174 _UPDATE_SQL = 'update.sql'175 _NODES_SQL = 'nodes.sql'176 _DELETE_SQL = 'delete.sql'177 178 parent_ids = [179 {'type': 'int', 'id': 'gid'},180 {'type': 'int', 'id': 'sid'}181 ]182 ids = [183 {'type': 'int', 'id': 'rg_id'}184 ]185 186 operations = dict({187 'obj': [188 {'get': 'properties', 'delete': 'delete', 'put': 'update'},189 {'get': 'list', 'post': 'create', 'delete': 'delete'}190 ],191 'nodes': [{'get': 'node'}, {'get': 'nodes'}],192 'sql': [{'get': 'sql'}],193 'msql': [{'get': 'msql'}, {'get': 'msql'}],194 'stats': [{'get': 'statistics'}],195 'dependency': [{'get': 'dependencies'}],196 'dependent': [{'get': 'dependents'}]197 })198 199 def __init__(self, **kwargs):200 """201 Method is used to initialize the ResourceGroupView and it's base view.202 Also initialize all the variables create/used dynamically like conn,203 template_path.204 205 Args:206 **kwargs:207 """208 self.conn = None209 self.template_path = None210 211 super().__init__(**kwargs)212 213 def check_precondition(f):214 """215 This function will behave as a decorator which will checks216 database connection before running view, it will also attaches217 manager,conn & template_path properties to self218 """219 220 @wraps(f)221 def wrap(*args, **kwargs):222 # Here args[0] will hold self & kwargs will hold gid,sid,did223 self = args[0]224 self.driver = get_driver(PG_DEFAULT_DRIVER)225 self.manager = self.driver.connection_manager(kwargs['sid'])226 self.conn = self.manager.connection()227 228 self.datistemplate = False229 if (230 self.manager.db_info is not None and231 self.manager.did in self.manager.db_info and232 'datistemplate' in self.manager.db_info[self.manager.did]233 ):234 self.datistemplate = self.manager.db_info[235 self.manager.did]['datistemplate']236 237 if not self.conn.connected():238 return precondition_required(239 gettext(240 "Connection to the server has been lost."241 )242 )243 self.sql_path = self.BASE_TEMPLATE_PATH.format(244 self.manager.version245 )246 return f(*args, **kwargs)247 248 return wrap249 250 @check_precondition251 def list(self, gid, sid):252 """253 This function is used to list all the resource group nodes within254 that collection.255 256 Args:257 gid: Server Group ID258 sid: Server ID259 """260 sql = render_template("/".join([self.sql_path, self._PROPERTIES_SQL]))261 status, res = self.conn.execute_dict(sql)262 263 if not status:264 return internal_server_error(errormsg=res)265 return ajax_response(266 response=res['rows'],267 status=200268 )269 270 @check_precondition271 def node(self, gid, sid, rg_id):272 """273 This function will used to create all the child node within that274 collection. Here it will create all the resource group node.275 276 Args:277 gid: Server Group ID278 sid: Server ID279 """280 281 sql = render_template("/".join([self.sql_path, self._NODES_SQL]),282 rgid=rg_id)283 status, result = self.conn.execute_2darray(sql)284 if not status:285 return internal_server_error(errormsg=result)286 287 if len(result['rows']) == 0:288 return gone(gettext("""Could not find the resource group."""))289 290 res = self.blueprint.generate_browser_node(291 result['rows'][0]['oid'],292 sid,293 result['rows'][0]['name'],294 icon="icon-resource_group"295 )296 297 return make_json_response(298 data=res,299 status=200300 )301 302 @check_precondition303 def nodes(self, gid, sid):304 """305 This function will used to create all the child node within that306 collection. Here it will create all the resource group node.307 308 Args:309 gid: Server Group ID310 sid: Server ID311 """312 res = []313 sql = render_template("/".join([self.sql_path, self._NODES_SQL]))314 status, result = self.conn.execute_2darray(sql)315 if not status:316 return internal_server_error(errormsg=result)317 318 for row in result['rows']:319 res.append(320 self.blueprint.generate_browser_node(321 row['oid'],322 sid,323 row['name'],324 icon="icon-resource_group"325 ))326 327 return make_json_response(328 data=res,329 status=200330 )331 332 @check_precondition333 def properties(self, gid, sid, rg_id):334 """335 This function will show the properties of the selected resource336 group node.337 338 Args:339 gid: Server Group ID340 sid: Server ID341 rg_id: Resource Group ID342 """343 sql = render_template(344 "/".join([self.sql_path, self._PROPERTIES_SQL]), rgid=rg_id)345 status, res = self.conn.execute_dict(sql)346 347 if not status:348 return internal_server_error(errormsg=res)349 350 if len(res['rows']) == 0:351 return gone(gettext("""Could not find the resource group."""))352 353 res['rows'][0]['is_sys_obj'] = (354 res['rows'][0]['oid'] <= DATABASE_LAST_SYSTEM_OID or355 self.datistemplate)356 357 return ajax_response(358 response=res['rows'][0],359 status=200360 )361 362 @staticmethod363 def _check_req_parameters(data, required_args):364 """365 This function is used to check the request parameter.366 :param data:367 :param required_args:368 :return:369 """370 for arg in required_args:371 if arg not in data:372 return True, make_json_response(373 status=410,374 success=0,375 errormsg=gettext(376 "Could not find the required parameter ({})."377 ).format(arg)378 )379 return False, ''380 381 @check_precondition382 def create(self, gid, sid):383 """384 This function will create the new resource group object385 386 Args:387 gid: Server Group ID388 sid: Server ID389 """390 required_args = [391 'name'392 ]393 394 data = request.form if request.form else json.loads(395 request.data396 )397 398 is_error, errmsg = ResourceGroupView._check_req_parameters(399 data, required_args)400 401 if is_error:402 return errmsg403 404 try:405 # Below logic will create new resource group406 sql = render_template(407 "/".join([self.sql_path, self._CREATE_SQL]),408 rgname=data['name'], conn=self.conn409 )410 if sql and sql.strip('\n') and sql.strip(' '):411 status, res = self.conn.execute_scalar(sql)412 if not status:413 return internal_server_error(errormsg=res)414 # Below logic will update the cpu_rate_limit and dirty_rate_limit415 # for resource group, we need to add this logic because in416 # resource group you can't run multiple commands in one417 # transaction.418 sql = render_template(419 "/".join([self.sql_path, self._UPDATE_SQL]),420 data=data, conn=self.conn421 )422 # Checking if we are not executing empty query423 if sql and sql.strip('\n') and sql.strip(' '):424 status, res = self.conn.execute_scalar(sql)425 if not status:426 return internal_server_error(errormsg=res)427 # Below logic is used to fetch the oid of the newly created428 # resource group429 sql = render_template(430 "/".join([self.sql_path, 'getoid.sql']),431 rgname=data['name'], conn=self.conn432 )433 # Checking if we are not executing empty query434 rg_id = 0435 if sql and sql.strip('\n') and sql.strip(' '):436 status, rg_id = self.conn.execute_scalar(sql)437 if not status:438 return internal_server_error(errormsg=rg_id)439 440 return jsonify(441 node=self.blueprint.generate_browser_node(442 rg_id,443 sid,444 data['name'],445 icon="icon-resource_group"446 )447 )448 except Exception as e:449 return internal_server_error(errormsg=str(e))450 451 def _check_cpu_and_dirty_rate_limit(self, data, old_data):452 """453 Below logic will update the cpu_rate_limit and dirty_rate_limit454 for resource group we need to add this logic because in455 resource group you can't run multiple commands in one456 transaction.457 :param data:458 :param old_data:459 :return:460 """461 462 # Below logic will update the cpu_rate_limit and dirty_rate_limit463 # for resource group we need to add this logic because in464 # resource group you can't run multiple commands in one465 # transaction.466 if data['cpu_rate_limit'] != old_data['cpu_rate_limit'] or \467 data['dirty_rate_limit'] != old_data['dirty_rate_limit']:468 sql = render_template(469 "/".join([self.sql_path, self._UPDATE_SQL]),470 data=data, conn=self.conn471 )472 if sql and sql.strip('\n') and sql.strip(' '):473 status, res = self.conn.execute_scalar(sql)474 if not status:475 return internal_server_error(errormsg=res)476 477 @check_precondition478 def update(self, gid, sid, rg_id):479 """480 This function will update the data for the selected resource group node481 482 Args:483 gid: Server Group ID484 sid: Server ID485 rg_id: Resource Group ID486 """487 required_args = [488 'name', 'cpu_rate_limit', 'dirty_rate_limit'489 ]490 data = request.form if request.form else json.loads(491 request.data492 )493 494 try:495 sql = render_template(496 "/".join([self.sql_path, self._PROPERTIES_SQL]), rgid=rg_id)497 status, res = self.conn.execute_dict(sql)498 if not status:499 return internal_server_error(errormsg=res)500 old_data = res['rows'][0]501 for arg in required_args:502 if arg not in data:503 data[arg] = old_data[arg]504 505 if data['name'] != old_data['name']:506 sql = render_template(507 "/".join([self.sql_path, self._UPDATE_SQL]),508 oldname=old_data['name'], newname=data['name'],509 conn=self.conn510 )511 if sql and sql.strip('\n') and sql.strip(' '):512 status, res = self.conn.execute_scalar(sql)513 if not status:514 return internal_server_error(errormsg=res)515 516 # Below logic will update the cpu_rate_limit and dirty_rate_limit517 # for resource group we need to add this logic because in518 # resource group you can't run multiple commands in one519 # transaction.520 self._check_cpu_and_dirty_rate_limit(data, old_data)521 522 return jsonify(523 node=self.blueprint.generate_browser_node(524 rg_id,525 sid,526 data['name'],527 icon="icon-%s" % self.node_type528 )529 )530 531 except Exception as e:532 return internal_server_error(errormsg=str(e))533 534 @check_precondition535 def delete(self, gid, sid, rg_id=None):536 """537 This function will drop the resource group object538 539 Args:540 gid: Server Group ID541 sid: Server ID542 rg_id: Resource Group ID543 """544 if rg_id is None:545 data = request.form if request.form else json.loads(546 request.data547 )548 else:549 data = {'ids': [rg_id]}550 551 try:552 for rg_id in data['ids']:553 # Get name for resource group from rg_id554 sql = render_template(555 "/".join([self.sql_path, self._DELETE_SQL]),556 rgid=rg_id, conn=self.conn557 )558 status, rgname = self.conn.execute_scalar(sql)559 if not status:560 return internal_server_error(errormsg=rgname)561 562 if rgname is None:563 return make_json_response(564 success=0,565 errormsg=gettext(566 'Error: Object not found.'567 ),568 info=gettext(569 'The specified resource group '570 'could not be found.\n'571 )572 )573 574 # drop resource group575 sql = render_template(576 "/".join([self.sql_path, self._DELETE_SQL]),577 rgname=rgname, conn=self.conn578 )579 status, res = self.conn.execute_scalar(sql)580 if not status:581 return internal_server_error(errormsg=res)582 583 return make_json_response(584 success=1,585 info=gettext("Resource Group dropped")586 )587 588 except Exception as e:589 return internal_server_error(errormsg=str(e))590 591 @check_precondition592 def msql(self, gid, sid, rg_id=None):593 """594 This function is used to return modified SQL for the selected595 resource group node596 597 Args:598 gid: Server Group ID599 sid: Server ID600 rg_id: Resource Group ID601 """602 data = dict()603 for k, v in request.args.items():604 try:605 data[k] = json.loads(v)606 except ValueError:607 data[k] = v608 609 sql, _ = self.get_sql(data, rg_id)610 # Most probably this is due to error611 if not isinstance(sql, str):612 return sql613 614 sql = sql.strip('\n').strip(' ')615 616 if sql == '':617 sql = "--modified SQL"618 return make_json_response(619 data=sql,620 status=200621 )622 623 def _get_update_sql(self, rg_id, data, required_args):624 """625 This function is used to get the sql for resource group626 :param rg_id:627 :param data:628 :param required_args:629 :return:630 """631 sql = render_template(632 "/".join([self.sql_path, self._PROPERTIES_SQL]), rgid=rg_id)633 status, res = self.conn.execute_dict(sql)634 if not status:635 return internal_server_error(errormsg=res)636 637 if len(res['rows']) == 0:638 return gone(639 gettext("The specified resource group could not be found.")640 )641 old_data = res['rows'][0]642 for arg in required_args:643 if arg not in data:644 data[arg] = old_data[arg]645 646 sql = ''647 name_changed = False648 if data['name'] != old_data['name']:649 name_changed = True650 sql = render_template(651 "/".join([self.sql_path, self._UPDATE_SQL]),652 oldname=old_data['name'], newname=data['name'],653 conn=self.conn654 )655 if data['cpu_rate_limit'] != old_data['cpu_rate_limit'] or \656 data['dirty_rate_limit'] != old_data['dirty_rate_limit']:657 if name_changed:658 sql += "\n-- Following query will be executed in a " \659 "separate transaction\n"660 sql += render_template(661 "/".join([self.sql_path, self._UPDATE_SQL]),662 data=data, conn=self.conn663 )664 665 return sql, old_data['name']666 667 def get_sql(self, data, rg_id=None):668 """669 This function will generate sql from model data670 671 Args:672 data: Contains the value of name, cpu_rate_limit, dirty_rate_limit673 rg_id: Resource Group Id674 """675 required_args = [676 'name', 'cpu_rate_limit', 'dirty_rate_limit'677 ]678 679 old_name = ''680 if rg_id is not None:681 # Get sql for Resource group by ID.682 sql, old_name = self._get_update_sql(rg_id, data, required_args)683 else:684 sql = render_template(685 "/".join([self.sql_path, self._CREATE_SQL]),686 rgname=data['name'], conn=self.conn687 )688 689 cpu_rate_limit_flag = False690 dirty_rate_limit_flag = False691 if 'cpu_rate_limit' in data and data['cpu_rate_limit'] >= 0:692 cpu_rate_limit_flag = True693 694 if 'dirty_rate_limit' in data and data['dirty_rate_limit'] >= 0:695 dirty_rate_limit_flag = True696 697 if cpu_rate_limit_flag or dirty_rate_limit_flag:698 sql += "\n-- Following query will be executed in a " \699 "separate transaction\n"700 sql += render_template(701 "/".join([self.sql_path, self._UPDATE_SQL]),702 data=data, conn=self.conn703 )704 705 return sql, data['name'] if 'name' in data else old_name706 707 @check_precondition708 def sql(self, gid, sid, rg_id):709 """710 This function will generate sql for sql pane711 712 Args:713 gid: Server Group ID714 sid: Server ID715 rg_id: Resource Group ID716 """717 sql = render_template(718 "/".join([self.sql_path, self._PROPERTIES_SQL]), rgid=rg_id719 )720 status, res = self.conn.execute_dict(sql)721 if not status:722 return internal_server_error(errormsg=res)723 if len(res['rows']) == 0:724 return gone(725 gettext("The specified resource group could not be found.")726 )727 728 # Making copy of output for future use729 old_data = dict(res['rows'][0])730 731 sql = render_template(732 "/".join([self.sql_path, self._CREATE_SQL]),733 display_comments=True,734 rgname=old_data['name'], conn=self.conn735 )736 sql += "\n"737 sql += render_template(738 "/".join([self.sql_path, self._UPDATE_SQL]),739 data=old_data, conn=self.conn740 )741 742 return ajax_response(response=sql)743 744 745ResourceGroupView.register_node_view(blueprint)746 