state machine integration wip

This commit is contained in:
tomdds
2020-01-16 13:44:10 -05:00
parent 187ee0033e
commit b1adaf771d
6 changed files with 204 additions and 59 deletions

View File

@@ -10,6 +10,9 @@ class StageStates(Enum):
class AzureStages(Enum):
TENANT = "tenant"
BILLING_PROFILE = "billing profile"
BILLING_PROFILE_TENANT_ACCESS = "billing profile tenant access"
TASK_ORDER_BILLING = "task order billing"
BILLING_INSTRUCTION = "billing instruction"
def _build_csp_states(csp_stages):

View File

@@ -1,3 +1,7 @@
from random import choice, choices
import re
import string
from sqlalchemy import Column, ForeignKey, Enum as SQLAEnum
from sqlalchemy.orm import relationship, reconstructor
from sqlalchemy.dialects.postgresql import UUID
@@ -17,6 +21,16 @@ import atst.models.mixins as mixins
from atst.models.mixins.state_machines import FSMStates, AzureStages, _build_transitions
def make_password():
return choice(string.ascii_letters) + "".join(
choices(string.ascii_letters + string.digits + string.punctuation, k=15)
)
def fetch_portfolio_creds(portfolio):
return dict(username="mock-cloud", password="shh")
@add_state_features(Tags)
class StateMachineWithTags(Machine):
pass
@@ -73,57 +87,49 @@ class PortfolioStateMachine(
return getattr(FSMStates, self.state)
return self.state
def trigger_next_transition(self):
def trigger_next_transition(self, **kwargs):
state_obj = self.machine.get_state(self.state)
if state_obj.is_system:
if self.current_state in (FSMStates.UNSTARTED, FSMStates.STARTING):
# call the first trigger availabe for these two system states
trigger_name = self.machine.get_triggers(self.current_state.name)[0]
self.trigger(trigger_name)
self.trigger(trigger_name, **kwargs)
elif self.current_state == FSMStates.STARTED:
# get the first trigger that starts with 'create_'
create_trigger = self._get_first_stage_create_trigger()
if create_trigger:
self.trigger(create_trigger)
self.trigger(create_trigger, **kwargs)
else:
self.fail_stage(stage)
elif state_obj.is_IN_PROGRESS:
pass
# elif state_obj.is_TENANT:
# pass
# elif state_obj.is_BILLING_PROFILE:
# pass
elif state_obj.is_CREATED:
triggers = self.machine.get_triggers(state_obj.name)
self.trigger(triggers[-1], **kwargs)
# @with_payload
def after_in_progress_callback(self, event):
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"},)
# Accumulate payload w/ creds
payload = event.kwargs.get("csp_data")
payload["creds"] = event.kwargs.get("creds")
payload_data_cls = get_stage_csp_class(stage, "payload")
if not payload_data_cls:
print("could not resolve payload data class")
self.fail_stage(stage)
try:
payload_data = payload_data_cls(**payload)
except PydanticValidationError as exc:
print("Payload Validation Error:")
print(exc.json())
print("got")
print(payload)
self.fail_stage(stage)
# TODO: Determine best place to do this, maybe @reconstructor
csp = event.kwargs.get("csp")
if csp is not None:
self.csp = AzureCSP(app).cloud
@@ -132,7 +138,8 @@ class PortfolioStateMachine(
for attempt in range(5):
try:
response = getattr(self.csp, "create_" + stage)(payload_data)
func_name = f"create_{stage}"
response = getattr(self.csp, func_name)(payload_data)
except (ConnectionException, UnknownServerException) as exc:
print("caught exception. retry", attempt)
continue
@@ -140,14 +147,17 @@ class PortfolioStateMachine(
break
else:
# failed all attempts
print("failed")
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.update(response)
db.session.add(self.portfolio)
db.session.commit()
# store any updated creds, if necessary
self.finish_stage(stage)
def is_csp_data_valid(self, event):
@@ -156,16 +166,23 @@ class PortfolioStateMachine(
if self.portfolio.csp_data is None or not isinstance(
self.portfolio.csp_data, dict
):
print("no csp data")
return False
stage = self.current_state.name.split("_IN_PROGRESS")[0].lower()
stage_data = self.portfolio.csp_data.get(stage + "_data")
stage_data = self.portfolio.csp_data
cls = get_stage_csp_class(stage, "result")
if not cls:
return False
try:
cls(**stage_data)
dc = cls(**stage_data)
if getattr(dc, "get_creds", None) is not None:
new_creds = dc.get_creds()
# TODO: how/where to store these
# TODO: credential schema
# self.store_creds(self.portfolio, new_creds)
except PydanticValidationError as exc:
print(exc.json())
return False