portfolio provision process. add logging, __repr___ method, fix broken tests

This commit is contained in:
Philip Kalinsky 2020-01-23 10:14:22 -05:00
parent 00b10c484f
commit 597ea32e42
4 changed files with 50 additions and 31 deletions

View File

@ -4,6 +4,8 @@ from uuid import uuid4
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from flask import current_app as app
from atst.models.user import User from atst.models.user import User
from atst.models.application import Application from atst.models.application import Application
from atst.models.environment import Environment from atst.models.environment import Environment
@ -754,6 +756,7 @@ class AzureSDKProvider(object):
self.graphrbac = graphrbac self.graphrbac = graphrbac
self.credentials = credentials self.credentials = credentials
self.identity = identity self.identity = identity
self.exceptions = exceptions
self.secrets = secrets self.secrets = secrets
self.requests = requests self.requests = requests
# may change to a JEDI cloud # may change to a JEDI cloud
@ -781,14 +784,21 @@ class AzureCloudProvider(CloudProviderInterface):
secret_client = self.secrets.SecretClient( secret_client = self.secrets.SecretClient(
vault_url=self.vault_url, credential=credential, vault_url=self.vault_url, credential=credential,
) )
return secret_client.set_secret(secret_key, secret_value) try:
return secret_client.set_secret(secret_key, secret_value)
except self.exceptions.HttpResponseError as exc:
app.logger.error(f"Could not SET secret in Azure keyvault for key {secret_key}.", exc_info=1)
def get_secret(secret_key): def get_secret(secret_key):
credential = self._get_client_secret_credential_obj() credential = self._get_client_secret_credential_obj()
secret_client = self.secrets.SecretClient( secret_client = self.secrets.SecretClient(
vault_url=self.vault_url, credential=credential, vault_url=self.vault_url, credential=credential,
) )
return secret_client.get_secret(secret_key).value try:
return secret_client.get_secret(secret_key).value
except self.exceptions.HttpResponseError as exc:
app.logger.error(f"Could not GET secret in Azure keyvault for key {secret_key}.", exc_info=1)
def create_environment( def create_environment(
self, auth_credentials: Dict, user: User, environment: Environment self, auth_credentials: Dict, user: User, environment: Environment

View File

@ -1,5 +1,7 @@
from enum import Enum from enum import Enum
from flask import current_app as app
class StageStates(Enum): class StageStates(Enum):
CREATED = "created" CREATED = "created"
@ -107,10 +109,12 @@ class FSMMixin:
fail_trigger = "fail" + stage fail_trigger = "fail" + stage
if fail_trigger in self.machine.get_triggers(self.current_state.name): if fail_trigger in self.machine.get_triggers(self.current_state.name):
self.trigger(fail_trigger) self.trigger(fail_trigger)
app.logger.info(f"calling fail trigger '{fail_trigger}' for '{self.__repr__()}'")
def finish_stage(self, stage): 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): if finish_trigger in self.machine.get_triggers(self.current_state.name):
app.logger.info(f"calling finish trigger '{finish_trigger}' for '{self.__repr__()}'")
self.trigger(finish_trigger) self.trigger(finish_trigger)
def prepare_init(self, event): def prepare_init(self, event):

View File

@ -64,6 +64,10 @@ class PortfolioStateMachine(
db.session.add(self) db.session.add(self)
db.session.commit() db.session.commit()
def __repr__(self):
return f"<PortfolioStateMachine(state='{self.current_state.name}', portfolio='{self.portfolio.name}'"
@reconstructor @reconstructor
def attach_machine(self): def attach_machine(self):
""" """
@ -108,6 +112,7 @@ class PortfolioStateMachine(
if create_trigger: if create_trigger:
self.trigger(create_trigger, **kwargs) self.trigger(create_trigger, **kwargs)
else: else:
app.logger.info(f"could not locate 'create trigger' for {self.__repr__()}")
self.fail_stage(stage) self.fail_stage(stage)
elif state_obj.is_CREATED: elif state_obj.is_CREATED:
@ -133,15 +138,14 @@ class PortfolioStateMachine(
payload_data_cls = get_stage_csp_class(stage, "payload") payload_data_cls = get_stage_csp_class(stage, "payload")
if not payload_data_cls: if not payload_data_cls:
print("could not resolve payload data class") app.logger.info(f"could not resolve payload data class for stage {stage}")
self.fail_stage(stage) self.fail_stage(stage)
try: try:
payload_data = payload_data_cls(**payload) payload_data = payload_data_cls(**payload)
except PydanticValidationError as exc: except PydanticValidationError as exc:
print("Payload Validation Error:") app.logger.error(f"Payload Validation Error in {self.__repr__()}:", exc_info=1)
print(exc.json()) app.logger.info(exc.json())
print("got") app.logger.info(payload)
print(payload)
self.fail_stage(stage) self.fail_stage(stage)
# TODO: Determine best place to do this, maybe @reconstructor # TODO: Determine best place to do this, maybe @reconstructor
@ -151,18 +155,19 @@ class PortfolioStateMachine(
else: else:
self.csp = MockCSP(app).cloud self.csp = MockCSP(app).cloud
for attempt in range(5): attempts_count = 5
for attempt in range(attempts_count):
try: try:
func_name = f"create_{stage}" func_name = f"create_{stage}"
response = getattr(self.csp, func_name)(payload_data) response = getattr(self.csp, func_name)(payload_data)
except (ConnectionException, UnknownServerException) as exc: except (ConnectionException, UnknownServerException) as exc:
print("caught exception. retry", attempt) app.logger.error(f"CSP api call. Caught exception for {self.__repr__()}. Retry attempt {attempt}", exc_info=1)
continue continue
else: else:
break break
else: else:
# failed all attempts # failed all attempts
print("failed") logger.info(f"CSP api call failed after {attempts_count} attempts.")
self.fail_stage(stage) self.fail_stage(stage)
if self.portfolio.csp_data is None: if self.portfolio.csp_data is None:
@ -177,7 +182,6 @@ class PortfolioStateMachine(
def is_csp_data_valid(self, event): def is_csp_data_valid(self, event):
# check portfolio csp details json field for fields # check portfolio csp details json field for fields
if self.portfolio.csp_data is None or not isinstance( if self.portfolio.csp_data is None or not isinstance(
self.portfolio.csp_data, dict self.portfolio.csp_data, dict
): ):
@ -199,9 +203,10 @@ class PortfolioStateMachine(
# self.store_creds(self.portfolio, new_creds) # self.store_creds(self.portfolio, new_creds)
except PydanticValidationError as exc: except PydanticValidationError as exc:
print("is_csp_data_valid: False") app.logger.error(f"Payload Validation Error in {self.__repr__()}:", exc_info=1)
print(cls) app.logger.info(exc.json())
print(exc.json()) app.logger.info(payload)
return False return False
return True return True

View File

@ -40,13 +40,6 @@ def test_state_machine_compose_state(portfolio):
) )
def test_state_machine_first_stage_create_trigger(portfolio):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
first_stage_create_trigger = sm._get_first_stage_create_trigger()
first_stage_name = list(AzureStages)[0].name.lower()
assert "create_" + first_stage_name == first_stage_create_trigger
def test_state_machine_valid_data_classes_for_stages(portfolio): def test_state_machine_valid_data_classes_for_stages(portfolio):
sm = PortfolioStateMachineFactory.create(portfolio=portfolio) sm = PortfolioStateMachineFactory.create(portfolio=portfolio)
for stage in AzureStages: for stage in AzureStages:
@ -74,8 +67,14 @@ def test_state_machine_initialization(portfolio):
] == in_progress_triggers ] == in_progress_triggers
started_triggers = sm.machine.get_triggers("STARTED") started_triggers = sm.machine.get_triggers("STARTED")
first_stage_create_trigger = sm._get_first_stage_create_trigger() create_trigger = next(
assert ["reset", "fail", first_stage_create_trigger] == started_triggers filter(
lambda trigger: trigger.startswith("create_"),
sm.machine.get_triggers(FSMStates.STARTED.name),
),
None,
)
assert ["reset", "fail", create_trigger] == started_triggers
def test_fsm_transition_start(portfolio): def test_fsm_transition_start(portfolio):
@ -96,18 +95,19 @@ def test_fsm_transition_start(portfolio):
else: else:
csp_data = {} csp_data = {}
ppoc = portfolio.owner # ppoc = portfolio.owner
user_id = f"{ppoc.first_name[0]}{ppoc.last_name}".lower() # user_id = f"{ppoc.first_name[0]}{ppoc.last_name}".lower()
user_id = "abcdefg"
domain_name = re.sub("[^0-9a-zA-Z]+", "", portfolio.name).lower() domain_name = re.sub("[^0-9a-zA-Z]+", "", portfolio.name).lower()
portfolio_data = { portfolio_data = {
"user_id": user_id, "user_id": user_id,
"password": "jklfsdNCVD83nklds2#202", "password": "jklfsdNCVD83nklds2#202",
"domain_name": domain_name, "domain_name": domain_name,
"first_name": ppoc.first_name, "first_name": "john", # ppoc.first_name,
"last_name": ppoc.last_name, "last_name": "doe", # ppoc.last_name,
"country_code": "US", "country_code": "US",
"password_recovery_email_address": ppoc.email, "password_recovery_email_address": "email@example.com", # ppoc.email,
"address": { "address": {
"company_name": "", "company_name": "",
"address_line_1": "", "address_line_1": "",
@ -128,7 +128,7 @@ def test_fsm_transition_start(portfolio):
assert sm.state == FSMStates.TENANT_CREATED assert sm.state == FSMStates.TENANT_CREATED
assert portfolio.csp_data.get("tenant_id", None) is not None assert portfolio.csp_data.get("tenant_id", None) is not None
print(portfolio.csp_data.keys()) #print(portfolio.csp_data.keys())
if portfolio.csp_data is not None: if portfolio.csp_data is not None:
csp_data = portfolio.csp_data csp_data = portfolio.csp_data
else: else:
@ -137,6 +137,6 @@ def test_fsm_transition_start(portfolio):
list(csp_data.items()) + list(portfolio_data.items()) + list(config.items()) list(csp_data.items()) + list(portfolio_data.items()) + list(config.items())
) )
sm.trigger_next_transition(creds=creds, csp_data=collected_data) sm.trigger_next_transition(creds=creds, csp_data=collected_data)
assert sm.state == FSMStates.BILLING_PROFILE_CREATED assert sm.state == FSMStates.BILLING_PROFILE_CREATION_CREATED
print(portfolio.csp_data.keys()) #print(portfolio.csp_data.keys())