Fix formatting and some typos

This commit is contained in:
tomdds
2020-01-14 15:21:02 -05:00
parent 34546ecd94
commit d81d953c31
14 changed files with 189 additions and 141 deletions

View File

@@ -7,7 +7,11 @@ from .audit_event import AuditEvent
from .clin import CLIN, JEDICLINType
from .environment import Environment
from .environment_role import EnvironmentRole, CSPRole
from .job_failure import EnvironmentJobFailure, EnvironmentRoleJobFailure, PortfolioJobFailure
from .job_failure import (
EnvironmentJobFailure,
EnvironmentRoleJobFailure,
PortfolioJobFailure,
)
from .notification_recipient import NotificationRecipient
from .permissions import Permissions
from .permission_set import PermissionSet

View File

@@ -15,8 +15,8 @@ class EnvironmentRoleJobFailure(Base, mixins.JobFailureMixin):
environment_role_id = Column(ForeignKey("environment_roles.id"), nullable=False)
class PortfolioJobFailure(Base, mixins.JobFailureMixin):
__tablename__ = "portfolio_job_failures"
portfolio_id = Column(ForeignKey("portfolios.id"), nullable=False)

View File

@@ -1,109 +1,137 @@
from enum import Enum
from atst.database import db
class StageStates(Enum):
CREATED = "created"
IN_PROGRESS = "in progress"
FAILED = "failed"
class AzureStages(Enum):
TENANT = "tenant"
BILLING_PROFILE = "billing profile"
ADMIN_SUBSCRIPTION = "admin subscription"
def _build_csp_states(csp_stages):
states = {
'UNSTARTED' : "unstarted",
'STARTING' : "starting",
'STARTED' : "started",
'COMPLETED' : "completed",
'FAILED' : "failed",
"UNSTARTED": "unstarted",
"STARTING": "starting",
"STARTED": "started",
"COMPLETED": "completed",
"FAILED": "failed",
}
for csp_stage in csp_stages:
for state in StageStates:
states[csp_stage.name+"_"+state.name] = csp_stage.value+" "+state.value
states[csp_stage.name + "_" + state.name] = (
csp_stage.value + " " + state.value
)
return states
FSMStates = Enum('FSMStates', _build_csp_states(AzureStages))
FSMStates = Enum("FSMStates", _build_csp_states(AzureStages))
def _build_transitions(csp_stages):
transitions = []
states = []
compose_state = lambda csp_stage, state: getattr(FSMStates, "_".join([csp_stage.name, state.name]))
compose_state = lambda csp_stage, state: getattr(
FSMStates, "_".join([csp_stage.name, state.name])
)
for stage_i, csp_stage in enumerate(csp_stages):
for state in StageStates:
states.append(dict(name=compose_state(csp_stage, state), tags=[csp_stage.name, state.name]))
states.append(
dict(
name=compose_state(csp_stage, state),
tags=[csp_stage.name, state.name],
)
)
if state == StageStates.CREATED:
if stage_i > 0:
src = compose_state(list(csp_stages)[stage_i-1] , StageStates.CREATED)
src = compose_state(
list(csp_stages)[stage_i - 1], StageStates.CREATED
)
else:
src = FSMStates.STARTED
transitions.append(
dict(
trigger='create_'+csp_stage.name.lower(),
trigger="create_" + csp_stage.name.lower(),
source=src,
dest=compose_state(csp_stage, StageStates.IN_PROGRESS),
after='after_in_progress_callback',
after="after_in_progress_callback",
)
)
if state == StageStates.IN_PROGRESS:
transitions.append(
dict(
trigger='finish_'+csp_stage.name.lower(),
trigger="finish_" + csp_stage.name.lower(),
source=compose_state(csp_stage, state),
dest=compose_state(csp_stage, StageStates.CREATED),
conditions=['is_csp_data_valid'],
conditions=["is_csp_data_valid"],
)
)
if state == StageStates.FAILED:
transitions.append(
dict(
trigger='fail_'+csp_stage.name.lower(),
trigger="fail_" + csp_stage.name.lower(),
source=compose_state(csp_stage, StageStates.IN_PROGRESS),
dest=compose_state(csp_stage, StageStates.FAILED),
)
)
return states, transitions
class FSMMixin():
class FSMMixin:
system_states = [
{'name': FSMStates.UNSTARTED.name, 'tags': ['system']},
{'name': FSMStates.STARTING.name, 'tags': ['system']},
{'name': FSMStates.STARTED.name, 'tags': ['system']},
{'name': FSMStates.FAILED.name, 'tags': ['system']},
{'name': FSMStates.COMPLETED.name, 'tags': ['system']},
{"name": FSMStates.UNSTARTED.name, "tags": ["system"]},
{"name": FSMStates.STARTING.name, "tags": ["system"]},
{"name": FSMStates.STARTED.name, "tags": ["system"]},
{"name": FSMStates.FAILED.name, "tags": ["system"]},
{"name": FSMStates.COMPLETED.name, "tags": ["system"]},
]
system_transitions = [
{'trigger': 'init', 'source': FSMStates.UNSTARTED, 'dest': FSMStates.STARTING},
{'trigger': 'start', 'source': FSMStates.STARTING, 'dest': FSMStates.STARTED},
{'trigger': 'reset', 'source': '*', 'dest': FSMStates.UNSTARTED},
{'trigger': 'fail', 'source': '*', 'dest': FSMStates.FAILED,}
{"trigger": "init", "source": FSMStates.UNSTARTED, "dest": FSMStates.STARTING},
{"trigger": "start", "source": FSMStates.STARTING, "dest": FSMStates.STARTED},
{"trigger": "reset", "source": "*", "dest": FSMStates.UNSTARTED},
{"trigger": "fail", "source": "*", "dest": FSMStates.FAILED,},
]
def prepare_init(self, event): pass
def before_init(self, event): pass
def after_init(self, event): pass
def prepare_init(self, event):
pass
def prepare_start(self, event): pass
def before_start(self, event): pass
def after_start(self, event): pass
def before_init(self, event):
pass
def prepare_reset(self, event): pass
def before_reset(self, event): pass
def after_reset(self, event): pass
def after_init(self, event):
pass
def prepare_start(self, event):
pass
def before_start(self, event):
pass
def after_start(self, event):
pass
def prepare_reset(self, event):
pass
def before_reset(self, event):
pass
def after_reset(self, event):
pass
def fail_stage(self, stage):
fail_trigger = 'fail'+stage
fail_trigger = "fail" + stage
if fail_trigger in self.machine.get_triggers(self.current_state.name):
self.trigger(fail_trigger)
def finish_stage(self, stage):
finish_trigger = 'finish_'+stage
finish_trigger = "finish_" + stage
if finish_trigger in self.machine.get_triggers(self.current_state.name):
self.trigger(finish_trigger)

View File

@@ -14,7 +14,6 @@ from atst.database import db
from sqlalchemy_json import NestedMutableJson
class Portfolio(
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
):
@@ -43,8 +42,9 @@ class Portfolio(
primaryjoin="and_(Application.portfolio_id == Portfolio.id, Application.deleted == False)",
)
state_machine = relationship("PortfolioStateMachine",
uselist=False, back_populates="portfolio")
state_machine = relationship(
"PortfolioStateMachine", uselist=False, back_populates="portfolio"
)
roles = relationship("PortfolioRole")

View File

@@ -1,5 +1,3 @@
import importlib
from sqlalchemy import Column, ForeignKey, Enum as SQLAEnum
from sqlalchemy.orm import relationship, reconstructor
from sqlalchemy.dialects.postgresql import UUID
@@ -13,36 +11,35 @@ from flask import current_app as app
from atst.domain.csp.cloud import ConnectionException, UnknownServerException
from atst.domain.csp import MockCSP, AzureCSP, get_stage_csp_class
from atst.database import db
from atst.queue import celery
from atst.models.types import Id
from atst.models.base import Base
import atst.models.mixins as mixins
from atst.models.mixins.state_machines import (
FSMStates, AzureStages, _build_transitions
)
from atst.models.mixins.state_machines import FSMStates, AzureStages, _build_transitions
@add_state_features(Tags)
class StateMachineWithTags(Machine):
pass
class PortfolioStateMachine(
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin, mixins.FSMMixin,
Base,
mixins.TimestampsMixin,
mixins.AuditableMixin,
mixins.DeletableMixin,
mixins.FSMMixin,
):
__tablename__ = "portfolio_state_machines"
id = Id()
portfolio_id = Column(
UUID(as_uuid=True),
ForeignKey("portfolios.id"),
)
portfolio_id = Column(UUID(as_uuid=True), ForeignKey("portfolios.id"),)
portfolio = relationship("Portfolio", back_populates="state_machine")
state = Column(
SQLAEnum(FSMStates, native_enum=False, create_constraint=False),
default=FSMStates.UNSTARTED, nullable=False
default=FSMStates.UNSTARTED,
nullable=False,
)
def __init__(self, portfolio, csp=None, **kwargs):
@@ -60,15 +57,15 @@ class PortfolioStateMachine(
Attach a machine depending on the current state.
"""
self.machine = StateMachineWithTags(
model = self,
send_event=True,
initial=self.current_state if self.state else FSMStates.UNSTARTED,
auto_transitions=False,
after_state_change='after_state_change',
model=self,
send_event=True,
initial=self.current_state if self.state else FSMStates.UNSTARTED,
auto_transitions=False,
after_state_change="after_state_change",
)
states, transitions = _build_transitions(AzureStages)
self.machine.add_states(self.system_states+states)
self.machine.add_transitions(self.system_transitions+transitions)
self.machine.add_states(self.system_states + states)
self.machine.add_transitions(self.system_transitions + transitions)
@property
def current_state(self):
@@ -87,37 +84,38 @@ class PortfolioStateMachine(
elif self.current_state == FSMStates.STARTED:
# get the first trigger that starts with 'create_'
create_trigger = list(filter(lambda trigger: trigger.startswith('create_'),
self.machine.get_triggers(FSMStates.STARTED.name)))[0]
create_trigger = list(
filter(
lambda trigger: trigger.startswith("create_"),
self.machine.get_triggers(FSMStates.STARTED.name),
)
)[0]
self.trigger(create_trigger)
elif state_obj.is_IN_PROGRESS:
pass
#elif state_obj.is_TENANT:
# elif state_obj.is_TENANT:
# pass
#elif state_obj.is_BILLING_PROFILE:
# elif state_obj.is_BILLING_PROFILE:
# pass
#@with_payload
# @with_payload
def after_in_progress_callback(self, event):
stage = self.current_state.name.split('_IN_PROGRESS')[0].lower()
if stage == 'tenant':
payload = dict(
creds={"username": "mock-cloud", "pass": "shh"},
user_id='123',
password='123',
domain_name='123',
first_name='john',
last_name='doe',
country_code='US',
password_recovery_email_address='password@email.com'
)
elif stage == 'billing_profile':
payload = dict(
stage = self.current_state.name.split("_IN_PROGRESS")[0].lower()
if stage == "tenant":
payload = dict( # nosec
creds={"username": "mock-cloud", "pass": "shh"},
user_id="123",
password="123",
domain_name="123",
first_name="john",
last_name="doe",
country_code="US",
password_recovery_email_address="password@email.com",
)
elif stage == "billing_profile":
payload = dict(creds={"username": "mock-cloud", "pass": "shh"},)
payload_data_cls = get_stage_csp_class(stage, "payload")
if not payload_data_cls:
@@ -128,7 +126,7 @@ class PortfolioStateMachine(
print(exc.json())
self.fail_stage(stage)
csp = event.kwargs.get('csp')
csp = event.kwargs.get("csp")
if csp is not None:
self.csp = AzureCSP(app).cloud
else:
@@ -136,18 +134,19 @@ class PortfolioStateMachine(
for attempt in range(5):
try:
response = getattr(self.csp, 'create_'+stage)(payload_data)
response = getattr(self.csp, "create_" + stage)(payload_data)
except (ConnectionException, UnknownServerException) as exc:
print('caught exception. retry', attempt)
print("caught exception. retry", attempt)
continue
else: break
else:
break
else:
# failed all attempts
self.fail_stage(stage)
if self.portfolio.csp_data is None:
self.portfolio.csp_data = {}
self.portfolio.csp_data[stage+"_data"] = response
self.portfolio.csp_data[stage + "_data"] = response
db.session.add(self.portfolio)
db.session.commit()
@@ -156,12 +155,13 @@ class PortfolioStateMachine(
def is_csp_data_valid(self, event):
# check portfolio csp details json field for fields
if self.portfolio.csp_data is None or \
not isinstance(self.portfolio.csp_data, dict):
if self.portfolio.csp_data is None or not isinstance(
self.portfolio.csp_data, dict
):
return False
stage = self.current_state.name.split('_IN_PROGRESS')[0].lower()
stage_data = self.portfolio.csp_data.get(stage+"_data")
stage = self.current_state.name.split("_IN_PROGRESS")[0].lower()
stage_data = self.portfolio.csp_data.get(stage + "_data")
cls = get_stage_csp_class(stage, "result")
if not cls:
return False
@@ -174,8 +174,7 @@ class PortfolioStateMachine(
return True
#print('failed condition', self.portfolio.csp_data)
# print('failed condition', self.portfolio.csp_data)
@property
def application_id(self):