def post_pg_control(http_context, app): # Control instance validate_parameters(http_context['post'], [ ('action', T_CONTROL, False) ]) action = http_context['post']['action'] logger.info("PostgreSQL '%s' requested." % action) NotificationMgmt.push(app.config, Notification(username=http_context['username'], message="PostgreSQL %s" % action)) cmd = app.config.administration.pg_ctl % action cmd_args = oneline_cmd_to_array(cmd) (rcode, stdout, stderr) = exec_script(cmd_args) if rcode != 0: raise Exception(str(stderr)) # Let's check if PostgreSQL is up & running after having executed # 'start' or 'restart' action. if action in ['start', 'restart']: # When a start/restart operation is requested, after the # startup/pg_ctl script has been executed then we check that # postgres is up & running: # while the PG conn. is not working then, for 10 seconds (max) # we'll check (connect/SELECT 1/disconnect) the connection, every # 0.5 second. retry = True t_start = time.time() while retry: try: with app.postgres.connect() as conn: conn.execute('SELECT 1') logger.info("Done.") return dict(action=action, state='ok') except error: if (time.time() - t_start) > 10: logger.info("Failed.") return dict(action=action, state='ko') logger.info("Retrying...") time.sleep(0.5) elif action == 'stop': # Check the PG conn is not working anymore. try: retry = True t_start = time.time() while retry: with app.postgres.connect() as conn: conn.execute('SELECT 1') time.sleep(0.5) if (time.time() - t_start) > 10: retry = False logger.info("Failed.") return dict(action=action, state='ko') except error: logger.info("Done.") return dict(action=action, state='ok') elif action == 'reload': logger.info("Done.") return dict(action=action, state='ok')
def post_hba_raw(conn, config, http_context): new_version = False # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="HBA file updated")) except NotificationError as e: logger.error(e.message) if 'content' not in http_context['post']: raise HTTPError(406, "Parameter 'content' not sent.") if http_context and 'new_version' in http_context['post']: # Check parameter 'version' validate_parameters(http_context['post'], [('new_version', T_NEW_VERSION, False)]) if http_context['post']['new_version'] is True: new_version = True hba_file = get_setting(conn, 'hba_file') return HBAManager.save_file_content(hba_file, http_context['post']['content'], new_version)
def get_settings(conn, http_context=None): filter_query = '' if http_context and 'filter' in http_context['query']: # Check 'filter' parameters. validate_parameters(http_context['query'], [('filter', T_PGSETTINGS_FILTER, True)]) filter = http_context['query']['filter'][0] filter_query = " WHERE name ILIKE '%{0}%'" \ " OR short_desc ILIKE '%{0}%'" \ " OR extra_desc ILIKE '%{0}%'".format(filter) query = """ SELECT name, setting, current_setting(name) AS current_setting, unit, vartype, min_val, max_val, enumvals, context, category, short_desc||' '||coalesce(extra_desc, '') AS desc, boot_val, reset_val, pending_restart FROM pg_settings %s ORDER BY category, name """ % (filter_query) ret = [] for row in conn.query(query): if http_context and len(http_context['urlvars']) > 0: if http_context['urlvars'][0] != row['category']: continue cat_exists = False i = 0 for ret_cat in ret: if ret_cat['category'] == row['category']: cat_exists = True break i += 1 enumvals = row['enumvals'] if enumvals is not None: # format enumvals as before switching from tpc to psycopg2 enumvals = '{%s}' % ','.join(enumvals) row_dict = { 'name': row['name'], 'setting': row['setting'], 'setting_raw': row['current_setting'], 'unit': row['unit'], 'vartype': row['vartype'], 'min_val': row['min_val'], 'max_val': row['max_val'], 'boot_val': row['boot_val'], 'reset_val': row['reset_val'], 'enumvals': enumvals, 'context': row['context'], 'desc': row['desc'], 'pending_restart': row['pending_restart'], } if not cat_exists: ret.append({'category': row['category'], 'rows': [row_dict]}) else: ret[i]['rows'].append(row_dict) return ret
def post_reindex(app, post, dbname, schema=None, table=None, index=None): # Parameters format validation if 'datetime' in post: validate_parameters(post, [ ('datetime', T_TIMESTAMP_UTC, False), ]) dt = post.get('datetime', datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")) with functions.get_postgres(app.config, dbname).connect() as conn: return functions.schedule_reindex( conn, dbname, dt, app, schema=schema, table=table, index=index)
def api_vacuum(http_context, queue_in = None, config = None, sessions = None, commands = None): set_logger_name("administration") worker = b'vacuum' # Get a new logger. logger = get_logger(config) try: check_sessionid(http_context['headers'], sessions) post = http_context['post'] # Check POST parameters. validate_parameters(post, [ ('database', T_OBJECTNAME, False), ('table', T_OBJECTNAME, False), ('mode', T_VACUUMMODE, False) ]) # Serialize parameters. parameters = base64.b64encode( pickle.dumps({ 'database': post['database'], 'table': post['table'], 'mode': post['mode'] })).decode('utf-8') except (Exception, HTTPError) as e: logger.traceback(get_tb()) logger.error(str(e)) if isinstance(e, HTTPError): raise e else: raise HTTPError(500, "Internal error.") # Check command uniqueness. try: commands.check_uniqueness(worker, parameters) except SharedItem_exists as e: logger.traceback(get_tb()) logger.error(str(e)) raise HTTPError(402, "Vaccum '%s' already running on table '%s'." % (post['mode'], post['table'])) cid = hash_id(worker + b'-' + parameters.encode('utf-8')) command = Command( cid.encode('utf-8'), time.time(), 0, worker, parameters, 0, u'') try: commands.add(command) # Put the Command in the command queue queue_in.put(command) return {"cid": cid} except SharedItem_no_free_slot_left as e: logger.traceback(get_tb()) logger.error(str(e)) raise HTTPError(500, "Internal error.")
def check_sessionid(http_header, sessions): validate_parameters(http_header, [('X-Session', T_SESSIONID, False)]) try: session = sessions.get_by_sessionid( http_header['X-Session'].encode('utf-8')) session.time = time.time() username = session.username sessions.update(session) return username except SharedItem_not_found: raise HTTPError(401, "Invalid session.")
def post_hba(conn, config, http_context): new_version = False set_logger_name("settings") logger = get_logger(config) # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="HBA file updated")) except NotificationError as e: logger.error(e.message) if 'entries' not in http_context['post']: raise HTTPError(406, "Parameter 'entries' not sent.") if http_context and 'new_version' in http_context['post']: # Check parameter 'version' validate_parameters(http_context['post'], [('new_version', T_NEW_VERSION, False)]) if http_context['post']['new_version'] is True: new_version = True hba_file = get_setting(conn, 'hba_file') hba_entries = [] logger.debug(http_context['post']['entries']) for entry in http_context['post']['entries']: if 'comment' in entry and len(entry['connection']) == 0: new_hba_entry = HBAComment() new_hba_entry.comment = entry['comment'] else: new_hba_entry = HBAEntry() try: new_hba_entry.connection = entry[ 'connection'] if 'connection' in entry else '' new_hba_entry.database = entry[ 'database'] if 'database' in entry else '' new_hba_entry.user = entry['user'] if 'user' in entry else '' new_hba_entry.address = entry[ 'address'] if 'address' in entry else '' new_hba_entry.auth_method = entry[ 'auth_method'] if 'auth_method' in entry else '' new_hba_entry.auth_options = entry[ 'auth_options'] if 'auth_options' in entry else '' except Exception as e: logger.error(e.message) raise HTTPError(406, "Invalid HBA entry.") new_hba_entry.lazy_check() hba_entries.append(new_hba_entry) return HBAManager.save_entries(hba_file, hba_entries, new_version)
def get_hba_raw(conn, config, http_context): version = None if http_context and 'version' in http_context['query']: # Check parameter 'version' validate_parameters(http_context['query'], [('version', T_FILE_VERSION, True)]) version = http_context['query']['version'][0] ret = {'filepath': None, 'version': version, 'content': ''} hba_file = get_setting(conn, 'hba_file') ret['filepath'] = hba_file ret['content'] = HBAManager.get_file_content(hba_file, version) return ret
def get_hba(conn, config, http_context): version = None if http_context and 'version' in http_context['query']: # Check parameter 'version' validate_parameters(http_context['query'], [('version', T_FILE_VERSION, True)]) version = http_context['query']['version'][0] ret = {'filepath': None, 'version': version, 'entries': []} hba_file = get_setting(conn, 'hba_file') ret['filepath'] = hba_file for hba_entry in HBAManager.get_entries(hba_file, version): ret['entries'].append(hba_entry.__dict__) return ret
def post_vacuum(app, post, dbname, schema=None, table=None): # Parameters format validation if 'datetime' in post: validate_parameters(post, [ ('datetime', T_TIMESTAMP_UTC, False), ]) dt = post.get('datetime', datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")) if 'mode' in post: validate_parameters(post, [ ('mode', T_VACUUM_MODE, False), ]) mode = post.get('mode', '') with functions.get_postgres(app.config, dbname).connect() as conn: return functions.schedule_vacuum(conn, dbname, mode, dt, app, schema=schema, table=table)
def post_hba_raw(conn, config, http_context): new_version = False if 'content' not in http_context['post']: raise HTTPError(406, "Parameter 'content' not sent.") if http_context and 'new_version' in http_context['post']: # Check parameter 'version' validate_parameters(http_context['post'], [('new_version', T_NEW_VERSION, False)]) if http_context['post']['new_version'] is True: new_version = True hba_file = get_setting(conn, 'hba_file') return HBAManager.save_file_content(hba_file, http_context['post']['content'], new_version)
def get_monitoring(http_context, app): """Monitoring root API aims to query metrics history. Data are sorted by collect timestamp, in ascending order. By default, only the most fresh record set is returned. The query parameter 'start' can be used as lower bound and must be expressed as a UTC timestamp formatted using ISO8601 norm with a terminal 'Z' character. To limit the number of returned records to N, the query parameter 'limit' can be used and set to N. 'limit' default value is 50, meaning that the maximum number of record set this API returns by default is 50. """ # Default values start_timestamp = None limit = 50 if 'start' in http_context['query']: # Validate start parameter validate_parameters(http_context['query'], [ ('start', T_TIMESTAMP_UTC, True), ]) # Convert it to epoch try: start_timestamp = ( datetime.strptime( http_context['query']['start'][0], "%Y-%m-%dT%H:%M:%SZ" ) - datetime(1970, 1, 1) ).total_seconds() except ValueError: raise TemboardHTTPError(406, "Invalid timestamp") if 'limit' in http_context['query']: # Validate limit parameter validate_parameters(http_context['query'], [ ('limit', T_LIMIT, True), ]) limit = int(http_context['query']['limit'][0]) return [ json.loads(metric[1]) for metric in db.get_metrics( app.config.temboard.home, 'monitoring.db', start_timestamp=start_timestamp, limit=limit ) ]
def say_hello_something2(config, http_context): """ "Hello <something>" using GET variable. Usage: $ export XSESSION=`curl -s -k -X POST --data '{"username":"******", "password":"******"}' https://localhost:2345/login | sed -E "s/^.+\"([a-f0-9]+)\".+$/\1/"` $ curl -s -k -H "X-Session:$XSESSION" "https://localhost:2345/hello2/say?something=toto" | python -m json.tool { "content": "Hello toto" } """ # noqa if http_context and 'something' in http_context['query']: validate_parameters(http_context['query'], [('something', T_SOMETHING, True)]) something = http_context['query']['something'][0] return {"content": "Hello %s" % (something)} else: raise HTTPError(444, "Parameter 'something' not sent.")
def post_activity_kill(conn, config, http_context): validate_parameters(http_context['post'], [('pids', T_PID, True)]) ret = {'backends': []} for pid in http_context['post']['pids']: conn.execute("SELECT pg_terminate_backend(%s) AS killed" % (pid)) # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="Backend %s terminated" % (pid))) except (NotificationError, Exception) as e: pass ret['backends'].append({ 'pid': pid, 'killed': list(conn.get_rows())[0]['killed'] }) return ret
def delete_hba_version(conn, config, http_context): version = None if http_context and 'version' in http_context['query']: # Check parameter 'version' validate_parameters(http_context['query'], [('version', T_FILE_VERSION, True)]) version = http_context['query']['version'][0] if version is None: raise HTTPError(406, "HBA version number must be specified.") hba_file = get_setting(conn, 'hba_file') # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="HBA file version '%s' removed." % (version))) except NotificationError as e: logger.error(e.message) return HBAManager.remove_version(hba_file, version)
def login(http_context, app, sessions): post = http_context['post'] # Add an unconditional sleeping time to reduce brute-force risks time.sleep(1) logger.info("Authenticating user: %s" % (post['username'])) try: validate_parameters(post, [('username', T_USERNAME, False), ('password', T_PASSWORD, False)]) auth_user(app.config.temboard['users'], post['username'], post['password']) except HTTPError as e: logger.info("Authentication failed.") raise e try: session = sessions.get_by_username(post['username']) if not session: sessionid = gen_sessionid(post['username']) session = Session(sessionid.encode('utf-8'), time.time(), post['username'].encode('utf-8')) sessions.add(session) else: sessionid = session.sessionid session.time = time.time() sessions.update(session) try: NotificationMgmt.push(app.config, Notification(username=post['username'], message="Login")) except NotificationError as e: logger.exception(e) return {'session': sessionid} except (SharedItem_exists, SharedItem_no_free_slot_left) as e: logger.exception(e) raise HTTPError(500, "Internal error.")
def post_activity_kill(conn, config, http_context): """ Kill (using pg_terminate_backend()) processes based on a given backend PID list. """ validate_parameters(http_context['post'], [('pids', T_PID, True)]) ret = {'backends': []} for pid in http_context['post']['pids']: killed = conn.query_scalar( "SELECT pg_terminate_backend(%s) AS killed" % (pid)) # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="Backend %s terminated" % (pid))) except (NotificationError, Exception): pass ret['backends'].append({ 'pid': pid, 'killed': killed, }) return ret
def get_hello2_say_query(http_context, config): """ "Hello <something>" using GET variable. Usage: $ export XSESSION=`curl -s -k -X POST --data '{"username":"******", "password":"******"}' https://localhost:2345/login | sed -E "s/^.+\"([a-f0-9]+)\".+$/\1/"` $ curl -s -k -H "X-Session:$XSESSION" "https://localhost:2345/hello2/say?something=toto" | python -m json.tool { "content": "Hello toto" } """ # noqa if http_context and 'something' in http_context['query']: validate_parameters(http_context['query'], [ ('something', T_SOMETHING, True) ])
def get_settings(conn, config, http_context=None): # get configuration from files files_conf = get_files_configuration(conn) # get auto config data_directory = get_setting(conn, 'data_directory') autoconfig_file = os.path.join(data_directory, 'postgresql.auto.conf') auto_conf = parse_configuration_file(autoconfig_file, {}) filter_query = '' if http_context and 'filter' in http_context['query']: # Check 'filter' parameters. validate_parameters(http_context['query'], [('filter', T_PGSETTINGS_FILTER, True)]) filter_query = " WHERE name ILIKE '%{0}%'" \ " OR short_desc ILIKE '%{0}%'" \ " OR extra_desc ILIKE '%{0}%'".format( http_context['query']['filter'][0]) query = """ SELECT name, setting, current_setting(name) AS current_setting, CASE WHEN name IN ('max_wal_size', 'min_wal_size') THEN current_setting('wal_segment_size') ELSE unit END AS unit, vartype, min_val, max_val, enumvals, context, category, short_desc||' '||coalesce(extra_desc, '') AS desc, boot_val FROM pg_settings %s ORDER BY category, name """ % (filter_query) conn.execute(query) ret = [] do_not_format_names = ['unix_socket_permissions'] for row in conn.get_rows(): if http_context and len(http_context['urlvars']) > 0: if http_context['urlvars'][0] != row['category']: continue cat_exists = False i = 0 for ret_cat in ret: if ret_cat['category'] == row['category']: cat_exists = True break i += 1 row_dict = { 'name': row['name'], 'setting': row['setting'], 'setting_raw': row['current_setting'], 'unit': row['unit'], 'vartype': row['vartype'], 'min_val': row['min_val'], 'max_val': row['max_val'], 'boot_val': row['boot_val'], 'auto_val': None, 'auto_val_raw': None, 'file_val': None, 'file_val_raw': None, 'enumvals': row['enumvals'], 'context': row['context'], 'desc': row['desc'] } name = row['name'] if name in auto_conf: setting = preformat(auto_conf[name].setting, row['vartype']) row_dict['auto_val'] = setting row_dict['auto_val_raw'] = setting if name in files_conf: setting = preformat(files_conf[name].setting, row['vartype']) row_dict['file_val'] = setting row_dict['file_val_raw'] = setting if name not in do_not_format_names: for e in [ 'setting', 'min_val', 'max_val', 'boot_val', 'auto_val', 'file_val' ]: row_dict[e] = format_setting(row_dict[e], row['vartype'], row_dict['unit']) if not cat_exists: ret.append({'category': row['category'], 'rows': [row_dict]}) else: ret[i]['rows'].append(row_dict) return ret
def get_settings(conn, config, http_context=None): auto_conf = get_auto_configuration(conn) file_conf = get_file_configuration(conn) filter_query = '' if http_context and 'filter' in http_context['query']: # Check 'filter' parameters. validate_parameters(http_context['query'], [('filter', T_PGSETTINGS_FILTER, True)]) filter_query = " WHERE name ILIKE '%%%s%%'" % ( http_context['query']['filter'][0], ) query = """ SELECT name, setting, current_setting(name) AS current_setting, CASE WHEN name IN ('max_wal_size', 'min_wal_size') THEN current_setting('wal_segment_size') ELSE unit END AS unit, vartype, min_val, max_val, enumvals, context, category, short_desc||' '||coalesce(extra_desc, '') AS desc, boot_val FROM pg_settings %s ORDER BY category, name """ % (filter_query) conn.execute(query) ret = [] do_not_format_names = ['unix_socket_permissions'] for row in conn.get_rows(): if http_context and len(http_context['urlvars']) > 0: if http_context['urlvars'][0] != row['category']: continue cat_exists = False i = 0 for ret_cat in ret: if ret_cat['category'] == row['category']: cat_exists = True break i += 1 prev_cat = row['category'] in_auto_conf = False if row['name'] in auto_conf: in_auto_conf = True in_file_conf = False if row['name'] in file_conf: in_file_conf = True row_dict = { 'name': row['name'], 'setting': row['setting'], 'setting_raw': row['current_setting'], 'unit': row['unit'], 'vartype': row['vartype'], 'min_val': row['min_val'], 'max_val': row['max_val'], 'boot_val': row['boot_val'], 'auto_val': None, 'auto_val_raw': None, 'file_val': None, 'file_val_raw': None, 'enumvals': row['enumvals'], 'context': row['context'], 'desc': row['desc'] } if in_auto_conf: val = auto_conf[row['name']] if val.startswith("'") and val.endswith("'"): val = val[1:-1] if row_dict['vartype'] == u'bool': if val == 'true': val = 'on' elif val == 'false': val = 'off' row_dict['auto_val'] = val row_dict['auto_val_raw'] = val if in_file_conf: val = file_conf[row['name']] if val.startswith("'") and val.endswith("'"): val = val[1:-1] if row_dict['vartype'] == u'bool': if val == 'true': val = 'on' elif val == 'false': val = 'off' row_dict['file_val'] = val row_dict['file_val_raw'] = val if row['name'] not in do_not_format_names and row[ 'vartype'] == u'integer': row_dict['setting'] = int(row_dict['setting']) row_dict['min_val'] = int(row_dict['min_val']) row_dict['max_val'] = int(row_dict['max_val']) row_dict['boot_val'] = int( human_to_number(row_dict['boot_val'], row_dict['unit'])) if in_auto_conf: row_dict['auto_val'] = int( human_to_number(row_dict['auto_val'], row_dict['unit'])) if in_file_conf: row_dict['file_val'] = int( human_to_number(row_dict['file_val'], row_dict['unit'])) elif row['name'] not in do_not_format_names and row[ 'vartype'] == u'real': row_dict['setting'] = float(row_dict['setting']) row_dict['min_val'] = float(row_dict['min_val']) row_dict['max_val'] = float(row_dict['max_val']) row_dict['boot_val'] = float(row_dict['boot_val']) if in_auto_conf: row_dict['auto_val'] = float(row_dict['auto_val']) if in_file_conf: row_dict['file_val'] = float(row_dict['file_val']) if not cat_exists: ret.append({'category': row['category'], 'rows': [row_dict]}) else: ret[i]['rows'].append(row_dict) return ret
def post_settings(conn, config, http_context): set_logger_name("settings") logger = get_logger(config) if http_context and 'filter' in http_context['query']: # Check 'filter' parameters. validate_parameters(http_context['query'], [('filter', T_PGSETTINGS_FILTER, True)]) pg_config_categories = get_settings(conn, config, None) if 'settings' not in http_context['post']: raise HTTPError(406, "Parameter 'settings' not sent.") settings = http_context['post']['settings'] ret = {'settings': []} do_not_check_names = ['unix_socket_permissions', 'log_file_mode'] logger.debug(settings) for setting in settings: if 'name' not in setting \ or 'setting' not in setting: raise HTTPError(406, "setting item malformed.") checked = False try: for pg_config_category in pg_config_categories: for pg_config_item in pg_config_category['rows']: if pg_config_item['name'] == setting['name']: if pg_config_item['name'] in do_not_check_names: checked = True raise Exception() if pg_config_item['vartype'] == u'integer': # Integers handling. if pg_config_item['min_val'] and pg_config_item['unit'] and \ (int(human_to_number(setting['setting'], pg_config_item['unit'])) < int(pg_config_item['min_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) if pg_config_item['max_val'] and pg_config_item['unit'] and \ (int(human_to_number(setting['setting'], pg_config_item['unit'])) > int(pg_config_item['max_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) setting['setting'] = pg_escape(setting['setting']) if ((setting['setting'].startswith("'") and setting['setting'].endswith("'")) or \ (setting['setting'].startswith('"') and setting['setting'].endswith('"'))): setting['setting'] = setting['setting'][1:-1] if setting['setting'] == '': setting['setting'] = None checked = True if pg_config_item['vartype'] == u'real': # Real handling. if pg_config_item['min_val'] and \ (float(setting['setting']) < float(pg_config_item['min_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) if pg_config_item['max_val'] and \ (float(setting['setting']) > float(pg_config_item['max_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) setting['setting'] = float(setting['setting']) checked = True if pg_config_item['vartype'] == u'bool': # Boolean handling. if setting['setting'].lower() not in [ u'on', u'off' ]: raise HTTPError( 406, 'Invalid setting: %s.' % (setting['setting'].lower())) checked = True if pg_config_item['vartype'] == u'enum': # Enum handling. if len(pg_config_item['enumvals']) > 0: enumvals = [ re.sub(r"^[\"\'](.+)[\"\ ']$", r"\1", enumval) for enumval in pg_config_item['enumvals'][1:-1].split(',') ] if ((setting['setting'].startswith("'") and setting['setting'].endswith("'")) or \ (setting['setting'].startswith('"') and setting['setting'].endswith('"'))): setting['setting'] = setting['setting'][ 1:-1] if setting['setting'] not in enumvals: raise HTTPError( 406, 'Invalid setting: %s.' % (setting['setting'])) checked = True if pg_config_item['vartype'] == u'string': # String handling. # setting must be escaped. setting['setting'] = pg_escape( str(setting['setting'])) if ((setting['setting'].startswith("'") and setting['setting'].endswith("'")) or \ (setting['setting'].startswith('"') and setting['setting'].endswith('"'))): setting['setting'] = setting['setting'][1:-1] if setting['setting'] == '': setting['setting'] = None checked = True raise Exception() except HTTPError as e: raise HTTPError(e.code, e.message['error']) except Exception as e: pass if not checked: raise HTTPError( 406, 'Parameter %s can\'t be checked.' % (setting['name'])) if 'force' not in setting: setting['force'] = 'false' if ((pg_config_item['vartype'] == u'integer' and setting['setting'] != pg_config_item['setting_raw']) or \ (pg_config_item['vartype'] == u'real' and float(setting['setting']) != float(pg_config_item['setting'])) or \ (pg_config_item['vartype'] not in [ u'integer', u'real' ] and setting['setting'] != pg_config_item['setting'])) or \ (setting['force'] == 'true'): # At this point, all incoming parameters have been checked. if setting['setting']: query = "ALTER SYSTEM SET %s TO '%s'" % (setting['name'], setting['setting']) else: query = "ALTER SYSTEM RESET %s;" % (setting['name']) logger.debug(query) # Push a notification on setting change. try: NotificationMgmt.push( config, Notification( username=http_context['username'], message="Setting '%s' changed: '%s' -> '%s'" % (pg_config_item['name'], pg_config_item['setting_raw'], setting['setting']))) except NotificationError as e: logger.error(e.message) try: conn.execute(query) except error as e: raise HTTPError(408, "%s: %s" % (setting['name'], e.message)) ret['settings'].append({ 'name': pg_config_item['name'], 'setting': setting['setting'], 'previous_setting': pg_config_item['setting_raw'], 'restart': True if pg_config_item['context'] in ['internal', 'postmaster'] else False }) # Reload PG configuration. conn.execute("SELECT pg_reload_conf()") # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="PostgreSQL reload")) except NotificationError as e: logger.error(e.message) return ret
def login(http_context, queue_in=None, config=None, sessions=None, commands=None): """ @api {get} /login User login @apiVersion 0.0.1 @apiName UserLogin @apiGroup User @apiParam {String} username Username. @apiParam {String} password Password. @apiSuccess {String} sessions Session ID. @apiExample {curl} Example usage: curl -k -X POST -H "Content-Type: application/json" -d '{"username": "******", "password": "******"}' \ https://localhost:2345/login @apiSuccessExample Success-Reponse: HTTP/1.0 200 OK Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:19:48 GMT Content-type: application/json {"session": "fa452548403ac53f2158a65f5eb6db9723d2b07238dd83f5b6d9ca52ce817b63"} @apiError (500 error) error Internal error. @apiError (404 error) error Invalid username or password. @apiError (406 error) error Username or password malformed or missing. @apiErrorExample 404 error example HTTP/1.0 404 Not Found Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:20:33 GMT Content-type: application/json {"error": "Invalid username/password."} @apiErrorExample 406 error example HTTP/1.0 406 Not Acceptable Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:21:01 GMT Content-type: application/json {"error": "Parameter 'password' is malformed."} """ post = http_context['post'] set_logger_name("api") logger = get_logger(config) # Add an unconditional sleeping time to reduce brute-force risks time.sleep(1) logger.info("Authenticating user: %s" % (post['username'])) try: validate_parameters(post, [('username', T_USERNAME, False), ('password', T_PASSWORD, False)]) auth_user(config.temboard['users'], post['username'], post['password']) except HTTPError as e: logger.traceback(get_tb()) logger.error(e.message) logger.info("Authentication failed.") raise e try: session = sessions.get_by_username(post['username']) if not session: sessionid = gen_sessionid(post['username']) session = Session(sessionid.encode('utf-8'), time.time(), post['username'].encode('utf-8')) sessions.add(session) else: sessionid = session.sessionid session.time = time.time() sessions.update(session) try: NotificationMgmt.push( config, Notification(username=post['username'], message="Login")) except NotificationError as e: logger.traceback(get_tb()) logger.error(e.message) except (SharedItem_exists, SharedItem_no_free_slot_left) as e: logger.traceback(get_tb()) logger.error(e.message) raise HTTPError(500, "Internal error.") return {'session': sessionid}
def post_pg_control(http_context, config=None, sessions=None): # NOTE: in this case we don't want to use api functions wrapper, it leads # to "Broken pipe" error with debian init.d script on start/restart. # This is probably due to getattr() call. post = http_context['post'] try: check_sessionid(http_context['headers'], sessions) # Check POST parameters. validate_parameters(post, [('action', T_CONTROL, False)]) session = sessions.get_by_sessionid( http_context['headers']['X-Session'].encode('utf-8')) except (Exception, HTTPError) as e: logger.exception(str(e)) logger.debug(http_context) if isinstance(e, HTTPError): raise e else: raise HTTPError(500, "Internal error.") try: NotificationMgmt.push( config, Notification(username=session.username, message="PostgreSQL %s" % post['action'])) except (NotificationError, Exception) as e: logger.exception(str(e)) try: logger.info("PostgreSQL '%s' requested." % (post['action'])) cmd_args = oneline_cmd_to_array( config.plugins['administration']['pg_ctl'] % (post['action'])) (rcode, stdout, stderr) = exec_script(cmd_args) if rcode != 0: raise Exception(str(stderr)) # Let's check if PostgreSQL is up & running after having executed # 'start' or 'restart' action. if post['action'] in ['start', 'restart']: conn = connector(host=config.postgresql['host'], port=config.postgresql['port'], user=config.postgresql['user'], password=config.postgresql['password'], database=config.postgresql['dbname']) # When a start/restart operation is requested, after the # startup/pg_ctl script has been executed then we check that # postgres is up & running: # while the PG conn. is not working then, for 10 seconds (max) # we'll check (connect/SELECT 1/disconnect) the connection, every # 0.5 second. retry = True t_start = time.time() while retry: try: conn.connect() conn.execute('SELECT 1') conn.close() logger.info("Done.") return {'action': post['action'], 'state': 'ok'} except error: if (time.time() - t_start) > 10: try: conn.close() except error: pass except Exception: pass logger.info("Failed.") return {'action': post['action'], 'state': 'ko'} time.sleep(0.5) elif post['action'] == 'stop': conn = connector(host=config.postgresql['host'], port=config.postgresql['port'], user=config.postgresql['user'], password=config.postgresql['password'], database=config.postgresql['dbname']) # Check the PG conn is not working anymore. try: retry = True t_start = time.time() while retry: conn.connect() conn.execute('SELECT 1') conn.close() time.sleep(0.5) if (time.time() - t_start) > 10: retry = False logger.info("Failed.") return {'action': post['action'], 'state': 'ko'} except error: logger.info("Done.") return {'action': post['action'], 'state': 'ok'} logger.info("Done.") return {'action': post['action'], 'state': 'ok'} except (Exception, error, HTTPError) as e: logger.exception(str(e)) logger.info("Failed") if isinstance(e, HTTPError): raise e else: raise HTTPError(500, "Internal error.")
def post_pg_control(http_context, queue_in = None, config = None, sessions = None, commands = None): # NOTE: in this case we don't want to use api functions wrapper, it leads # to "Broken pipe" error with debian init.d on start/restart. This is # probably due to getattr() call. set_logger_name("administration") # Get a new logger. logger = get_logger(config) check_sessionid(http_context['headers'], sessions) post = http_context['post'] # Check POST parameters. validate_parameters(post, [ ('action', T_CONTROL, False) ]) try: session = sessions.get_by_sessionid(http_context['headers']['X-Session'].encode('utf-8')) NotificationMgmt.push(config, Notification( username = session.username, message = "PostgreSQL %s" % (post['action']))) except NotificationError as e: logger.error(e.message) cmd_args = oneline_cmd_to_array(config.plugins['administration']['pg_ctl'] % (post['action'])) (rcode, stdout, stderr) = exec_script(cmd_args) if rcode != 0: raise HTTPError(408, str(stderr)) # Let's check if postgresql is up & running on 'start' or 'restart' action. if post['action'] in ['start', 'restart']: conn = connector( host = config.postgresql['host'], port = config.postgresql['port'], user = config.postgresql['user'], password = config.postgresql['password'], database = config.postgresql['dbname'] ) # When a start/restart operation is requested, after the startup/pg_ctl # script is executed we check that postgres is up & running: while the # PG connection is not working, during 10 seconds (max) we'll check # (connect/SELECT 1/disconnect) the connection, every 0.5 second. retry = True t_start = time.time() while retry: try: conn.connect() conn.execute('SELECT 1') conn.close() return {'action': post['action'], 'state': 'ok'} except error as e: if (time.time() - t_start) > 10: try: conn.close() except error as e: pass except Exception: pass return {'action': post['action'], 'state': 'ko'} time.sleep(0.5) elif post['action'] == 'stop': conn = connector( host = config.postgresql['host'], port = config.postgresql['port'], user = config.postgresql['user'], password = config.postgresql['password'], database = config.postgresql['dbname'] ) # Check the PG conn is not working anymore. try: retry = True t_start = time.time() while retry: conn.connect() conn.execute('SELECT 1') conn.close() time.sleep(0.5) if (time.time() - t_start) > 10: retry = False return {'action': post['action'], 'state': 'ko'} except error as e: return {'action': post['action'], 'state': 'ok'} return {'action': post['action'], 'state': 'ok'}