Merge pull request #1388 from dod-ccpo/portfolio-query

Update SQL query to find pending portfolios.
This commit is contained in:
dandds 2020-02-04 16:06:33 -05:00 committed by GitHub
commit 7c8fa1932a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 110 additions and 58 deletions

View File

@ -15,6 +15,8 @@ from atst.models import (
Permissions, Permissions,
PortfolioRole, PortfolioRole,
PortfolioRoleStatus, PortfolioRoleStatus,
TaskOrder,
CLIN,
) )
from .query import PortfoliosQuery, PortfolioStateMachinesQuery from .query import PortfoliosQuery, PortfolioStateMachinesQuery
@ -144,7 +146,7 @@ class Portfolios(object):
return db.session.query(Portfolio.id) return db.session.query(Portfolio.id)
@classmethod @classmethod
def get_portfolios_pending_provisioning(cls) -> List[UUID]: def get_portfolios_pending_provisioning(cls, now) -> List[UUID]:
""" """
Any portfolio with a corresponding State Machine that is either: Any portfolio with a corresponding State Machine that is either:
not started yet, not started yet,
@ -153,22 +155,18 @@ class Portfolios(object):
""" """
results = ( results = (
cls.base_provision_query() db.session.query(Portfolio.id)
.join(PortfolioStateMachine) .join(PortfolioStateMachine)
.join(TaskOrder)
.join(CLIN)
.filter(Portfolio.deleted == False)
.filter(CLIN.start_date <= now)
.filter(CLIN.end_date > now)
.filter( .filter(
or_( or_(
PortfolioStateMachine.state == FSMStates.UNSTARTED, PortfolioStateMachine.state == FSMStates.UNSTARTED,
PortfolioStateMachine.state == FSMStates.FAILED, PortfolioStateMachine.state.like("%CREATED"),
PortfolioStateMachine.state == FSMStates.TENANT_FAILED,
) )
) )
) )
return [id_ for id_, in results] return [id_ for id_, in results]
# db.session.query(PortfolioStateMachine).\
# filter(
# or_(
# PortfolioStateMachine.state==FSMStates.UNSTARTED,
# PortfolioStateMachine.state==FSMStates.UNSTARTED,
# )
# ).all()

View File

@ -203,7 +203,7 @@ def dispatch_provision_portfolio(self):
""" """
Iterate over portfolios with a corresponding State Machine that have not completed. Iterate over portfolios with a corresponding State Machine that have not completed.
""" """
for portfolio_id in Portfolios.get_portfolios_pending_provisioning(): for portfolio_id in Portfolios.get_portfolios_pending_provisioning(pendulum.now()):
provision_portfolio.delay(portfolio_id=portfolio_id) provision_portfolio.delay(portfolio_id=portfolio_id)

View File

@ -175,11 +175,14 @@ class PortfolioStateMachine(
app.logger.info(exc.json()) app.logger.info(exc.json())
print(exc.json()) print(exc.json())
app.logger.info(payload_data) app.logger.info(payload_data)
# TODO: Ensure that failing the stage does not preclude a Celery retry
self.fail_stage(stage) self.fail_stage(stage)
# TODO: catch and handle general CSP exception here
except (ConnectionException, UnknownServerException) as exc: except (ConnectionException, UnknownServerException) as exc:
app.logger.error( app.logger.error(
f"CSP api call. Caught exception for {self.__repr__()}.", exc_info=1, f"CSP api call. Caught exception for {self.__repr__()}.", exc_info=1,
) )
# TODO: Ensure that failing the stage does not preclude a Celery retry
self.fail_stage(stage) self.fail_stage(stage)
self.finish_stage(stage) self.finish_stage(stage)

View File

@ -1,5 +1,4 @@
import pytest import pytest
import pendulum
from uuid import uuid4 from uuid import uuid4
from atst.domain.environments import Environments from atst.domain.environments import Environments
@ -14,6 +13,7 @@ from tests.factories import (
EnvironmentRoleFactory, EnvironmentRoleFactory,
ApplicationRoleFactory, ApplicationRoleFactory,
) )
from tests.utils import EnvQueryTest
def test_create_environments(): def test_create_environments():
@ -119,40 +119,6 @@ def test_update_does_not_duplicate_names_within_application():
Environments.update(dupe_env, name) Environments.update(dupe_env, name)
class EnvQueryTest:
@property
def NOW(self):
return pendulum.now()
@property
def YESTERDAY(self):
return self.NOW.subtract(days=1)
@property
def TOMORROW(self):
return self.NOW.add(days=1)
def create_portfolio_with_clins(self, start_and_end_dates, env_data=None):
env_data = env_data or {}
return PortfolioFactory.create(
applications=[
{
"name": "Mos Eisley",
"description": "Where Han shot first",
"environments": [{"name": "thebar", **env_data}],
}
],
task_orders=[
{
"create_clins": [
{"start_date": start_date, "end_date": end_date}
for (start_date, end_date) in start_and_end_dates
]
}
],
)
class TestGetEnvironmentsPendingCreate(EnvQueryTest): class TestGetEnvironmentsPendingCreate(EnvQueryTest):
def test_with_expired_clins(self, session): def test_with_expired_clins(self, session):
self.create_portfolio_with_clins([(self.YESTERDAY, self.YESTERDAY)]) self.create_portfolio_with_clins([(self.YESTERDAY, self.YESTERDAY)])

View File

@ -26,6 +26,7 @@ from tests.factories import (
PortfolioStateMachineFactory, PortfolioStateMachineFactory,
get_all_portfolio_permission_sets, get_all_portfolio_permission_sets,
) )
from tests.utils import EnvQueryTest
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
@ -263,10 +264,44 @@ def test_create_state_machine(portfolio):
assert fsm assert fsm
def test_get_portfolios_pending_provisioning(session): class TestGetPortfoliosPendingCreate(EnvQueryTest):
for x in range(5): def test_finds_unstarted(self):
portfolio = PortfolioFactory.create() for x in range(5):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio) if x == 2:
if x == 2: state = "COMPLETED"
sm.state = FSMStates.COMPLETED else:
assert len(Portfolios.get_portfolios_pending_provisioning()) == 4 state = "UNSTARTED"
self.create_portfolio_with_clins(
[(self.YESTERDAY, self.TOMORROW)], state_machine_status=state
)
assert len(Portfolios.get_portfolios_pending_provisioning(self.NOW)) == 4
def test_finds_created(self):
self.create_portfolio_with_clins(
[(self.YESTERDAY, self.TOMORROW)], state_machine_status="TENANT_CREATED"
)
assert len(Portfolios.get_portfolios_pending_provisioning(self.NOW)) == 1
def test_does_not_find_failed(self):
self.create_portfolio_with_clins(
[(self.YESTERDAY, self.TOMORROW)], state_machine_status="TENANT_FAILED"
)
assert len(Portfolios.get_portfolios_pending_provisioning(self.NOW)) == 0
def test_with_expired_clins(self):
self.create_portfolio_with_clins([(self.YESTERDAY, self.YESTERDAY)])
assert len(Portfolios.get_portfolios_pending_provisioning(self.NOW)) == 0
def test_with_active_clins(self):
portfolio = self.create_portfolio_with_clins([(self.YESTERDAY, self.TOMORROW)])
Portfolios.get_portfolios_pending_provisioning(self.NOW) == [portfolio.id]
def test_with_future_clins(self):
self.create_portfolio_with_clins([(self.TOMORROW, self.TOMORROW)])
assert len(Portfolios.get_portfolios_pending_provisioning(self.NOW)) == 0
def test_with_already_provisioned_env(self):
self.create_portfolio_with_clins(
[(self.YESTERDAY, self.TOMORROW)], env_data={"cloud_id": uuid4().hex}
)
assert len(Portfolios.get_portfolios_pending_provisioning(self.NOW)) == 0

View File

@ -286,9 +286,19 @@ def test_create_environment_no_dupes(session, celery_app, celery_worker):
assert environment.claimed_until == None assert environment.claimed_until == None
def test_dispatch_provision_portfolio( def test_dispatch_provision_portfolio(csp, monkeypatch):
csp, session, portfolio, celery_app, celery_worker, monkeypatch portfolio = PortfolioFactory.create(
): task_orders=[
{
"create_clins": [
{
"start_date": pendulum.now().subtract(days=1),
"end_date": pendulum.now().add(days=1),
}
]
}
],
)
sm = PortfolioStateMachineFactory.create(portfolio=portfolio) sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
mock = Mock() mock = Mock()
monkeypatch.setattr("atst.jobs.provision_portfolio", mock) monkeypatch.setattr("atst.jobs.provision_portfolio", mock)

View File

@ -5,9 +5,12 @@ from unittest.mock import Mock
from OpenSSL import crypto from OpenSSL import crypto
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from flask import template_rendered from flask import template_rendered
import pendulum
from atst.utils.notification_sender import NotificationSender from atst.utils.notification_sender import NotificationSender
import tests.factories as factories
@contextmanager @contextmanager
def captured_templates(app): def captured_templates(app):
@ -62,3 +65,40 @@ def make_crl_list(x509_obj, x509_path):
issuer = x509_obj.issuer.public_bytes(default_backend()) issuer = x509_obj.issuer.public_bytes(default_backend())
filename = os.path.basename(x509_path) filename = os.path.basename(x509_path)
return [(filename, issuer.hex())] return [(filename, issuer.hex())]
class EnvQueryTest:
@property
def NOW(self):
return pendulum.now()
@property
def YESTERDAY(self):
return self.NOW.subtract(days=1)
@property
def TOMORROW(self):
return self.NOW.add(days=1)
def create_portfolio_with_clins(
self, start_and_end_dates, env_data=None, state_machine_status=None
):
env_data = env_data or {}
return factories.PortfolioFactory.create(
state=state_machine_status,
applications=[
{
"name": "Mos Eisley",
"description": "Where Han shot first",
"environments": [{"name": "thebar", **env_data}],
}
],
task_orders=[
{
"create_clins": [
{"start_date": start_date, "end_date": end_date}
for (start_date, end_date) in start_and_end_dates
]
}
],
)