Skip to content

Commit

Permalink
Merge pull request #37 from blue-yonder/development_nndo1991
Browse files Browse the repository at this point in the history
Prevent delete operation if there are active sessions in DB
  • Loading branch information
StephanErb committed Apr 3, 2020
2 parents 96cf19b + ace6c33 commit 613a784
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 27 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ docs/api/*
docs/_build/*
cover/*
MANIFEST
pyvenv.cfg
application.cfg
95 changes: 81 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ now your instance is created and as a response you get the details of your insta
"postgraas_instance_id": 1,
"container_id": "193f0d94d49fa26626fdbdb583e9453f923468b01eac59207b4852831a105c03",
"db_pwd": "secret",
"host": "not imlemented yet",
"host": "not implemented yet",
"db_name": "my_db",
"db_username": "db_user",
"port": 54648
Expand All @@ -75,27 +75,94 @@ Awesome, isn’t it?
Development
===========

Run the tests
-------------
You can follow the next steps in order to host postgraas_server locally and be able to develop features or bug fixes:

You need to have docker installed
Clone repository::

Make sure you pull the right docker image::
git clone https://github.com/blue-yonder/postgraas_server

docker pull postgres:9.4

Make a virtualenv and install the requirements including the dev requirements and a local editable install
of the package, for convenience you can install the requirements.in ::
Install all the project dependencies::

pip install -r requirements.in
pip install -r requirements_dev.txt
pip install -r requirements_docker.in
pip install -r requirements_prometheus.in
pip install gunicorn
pip install -e .

For the tests you need a running postgres meta database and set some environment variables accordingly.
There is a convenience script to set this all up using a docker postgres database::
Docker
-----------------

Pull the right docker image::

docker pull postgres:9.4

Your application.cfg file should look like this::

{
"metadb":
{
"db_name": "postgres",
"db_username": "postgres",
"db_pwd": "mysecret",
"host": "localhost",
"port": "5432"
},
"backend":
{
"type": "docker"
}
}

Initialize a postgres DB within a docker container::

sh setup_integration_test_docker.sh

Run a Docker container with the postgres image::

postgraas_init

Postgres Cluster
-----------------

If you don't want to use Docker as the backend you could create a local postgres cluster

Your application.cfg file should look like this::

{
"metadb":
{
"db_name": "postgres",
"db_username": "postgres",
"db_pwd": "mysecret",
"host": "localhost",
"port": "5432"
},
"backend":
{
"type": "pg_cluster",
"database": "postgres",
"username": "postgres",
"password": "mysecret",
"host": "localhost",
"port": "5432"
}
}

Run postgres server::

postgres -D /usr/local/var/postgres

Execute application locally
-----------------

Run the Flask application by executing this command::

python postgraas_server/postgraas_api.py

After this your application should be started and you can perform GET/POST/DELETE actions to this endppoint::

. setup_integration_test_docker.sh
http://localhost:5000/api/v2/postgraas_instances

Now you should be able to execute the tests::
Alternatively, you can run your unit and integration tests to verify your new code::

pytest tests/
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo',
'sphinx.ext.autosummary', 'sphinx.ext.viewcode', 'sphinx.ext.coverage',
'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.pngmath',
'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.imgmath',
'sphinx.ext.napoleon']

# Add any paths that contain templates here, relative to this directory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

def get_hostname():
# mmmh, how should this be done? need nome kind ouf routing...
return 'not imlemented yet'
return 'not implemented yet'


def _docker_client():
Expand Down
22 changes: 13 additions & 9 deletions postgraas_server/management_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from flask_sqlalchemy import SQLAlchemy

from postgraas_server.backends.exceptions import PostgraasApiException
from contextlib import closing

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -75,24 +76,27 @@ def delete(self, id):

entity = DBInstance.query.get(id)
if not entity:
abort(404, status='failed',
msg='Postgraas instance {} does not exist'.format(id)
)
abort(404, status='failed', msg='Postgraas instance {} does not exist'.format(id))

other_sessions = 0

try:
with psycopg2.connect(
with closing(psycopg2.connect(
user=entity.username,
password=args['db_pwd'],
host=current_app.postgraas_backend.master_hostname,
port=entity.port,
dbname=entity.db_name
):
pass
)) as conn:
cur = conn.cursor()
cur.execute("select count(pid) from pg_stat_activity where datname = %s ;", (entity.db_name,))
other_sessions = cur.fetchone()[0]-1
except Exception as ex:
return_code = 401 if 'authentication failed' in str(ex) else 500
abort(return_code, status='failed',
msg='Could not connect to postgres instance: {}'.format(str(ex))
)
abort(return_code, status='failed', msg='Could not connect to postgres instance: {}'.format(str(ex)))

if other_sessions > 0:
abort(409, status='failed', msg='Database contains other {} active sessions. Please close or terminate all sessions before deleting'.format(other_sessions))

if not current_app.postgraas_backend.exists(entity):
logger.warning(
Expand Down
4 changes: 2 additions & 2 deletions setup_integration_test_docker.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
## I hate setup bash scripts...but this one sets up a local postgres cluster for the postgres cluster backend...quite handy...
export PGDATABASE=postgres
export PGUSER=postgres@localhost
export PGUSER=postgres
export PGPASSWORD=mysecret
export PGPORT=54320
export PGPORT=5432
export PGHOST=localhost

docker run --name some-postgres -e POSTGRES_PASSWORD=$PGPASSWORD -e POSTGRES_USER=$PGUSER -e POSTGRES_DB=$PGDATABASE -p $PGPORT:5432 -d postgres
55 changes: 55 additions & 0 deletions tests/test_integration/test_postgraas_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import docker
import pytest
import psycopg2
from mock import patch, MagicMock, Mock
from contextlib import closing

import postgraas_server.backends.docker.postgres_instance_driver as pid
import postgraas_server.backends.postgres_cluster.postgres_cluster_driver as pgcd
Expand Down Expand Up @@ -402,6 +404,59 @@ def test_delete_notfound(self):
assert res['status'] == 'failed'
assert "123456789" in res['msg'], 'unexpected error message'

def test_delete_postgres_instance_api_with_active_sessions(self):
db_credentials = {
"postgraas_instance_name": "test_active_sessions_instance",
"db_name": "test_active_sessions_db",
"db_username": "test_active_sessions_user",
"db_pwd": "secret"
}
headers = {'Content-Type': 'application/json'}
result = self.app_client.post(
'/api/v2/postgraas_instances', headers=headers, data=json.dumps(db_credentials)
)
created_db = json.loads(result.get_data(as_text=True))

if self.backend == 'docker':
wait_success = wait_for_postgres_listening(created_db['container_id'])
assert wait_success is True, 'postgres did not come up within 10s (or unexpected docker image log output)'

try:
with closing(psycopg2.connect(
user=db_credentials["db_username"],
password=db_credentials["db_pwd"],
host=os.environ.get('PGHOST', 'localhost'),
port=created_db["port"],
dbname=db_credentials["db_name"]
)):
delete_result = self.app_client.delete(
'/api/v2/postgraas_instances/' + str(created_db["postgraas_instance_id"]),
data=json.dumps({
'db_pwd': db_credentials['db_pwd']
}),
headers=headers
)
deleted_db = json.loads(delete_result.get_data(as_text=True))
assert delete_result.status_code == 409
assert deleted_db["status"] == 'failed'
assert 'active sessions' in deleted_db[
'msg'
], 'unexpected message for active sessions in the database'
except Exception:
pass

delete_result = self.app_client.delete(
'/api/v2/postgraas_instances/' + str(created_db["postgraas_instance_id"]),
data=json.dumps({
'db_pwd': db_credentials['db_pwd']
}),
headers=headers
)

deleted_db = json.loads(delete_result.get_data(as_text=True))
assert delete_result.status_code == 200
assert deleted_db["status"] == 'success'

def test_create_postgres_instance_name_exists(self):
db_credentials = {
"postgraas_instance_name": "tests_postgraas_my_postgraas_twice",
Expand Down

0 comments on commit 613a784

Please sign in to comment.