<jupyter_start><jupyter_text>This noteboook launches the solution, with a parameter that instructs the instance to run the solution's notebook using papermill, wait for that process to finish, then raise any errors encountered while running the notebook to the build.The _build instance_ will launch the solution using the following parameters, which can be overriden by providing them as enviroment variables in the build settings. Since the build instance is launching the solution, the build project needs to be provided with all the permissions that are necessary to launch the solution.<jupyter_code>BRANCH="mainline"
REGION="us-west-2"
SOLUTIONS_BUCKET="sagemaker-solutions-devo"
SOLUTION_NAME="Sagemaker-purchase-modelling"
STACK_NAME="sagemaker-soln-pmdl-ci"
STACK_VERSION="development"
COMMIT_ID = ""
EXECUTION_ID = ""
CI_BUCKET = ""
NOTEBOOK_POLL_ATTEMPTS=120 # Number of attempts while waiting for SM notebook to execute and produce output on S3
NOTEBOOK_POLL_DELAY=60 # Delay between each attempt, in seconds<jupyter_output><empty_output><jupyter_text>The next cell programmatically creates the URL for the solution's template, based on the parameters passed above. It's important to include the branch suffix to be able to support feature branches as well as the mainline release pipeline.<jupyter_code>branch_suffix = "" if BRANCH == "mainline" else f"-{BRANCH}"
template_url = f"https://{SOLUTIONS_BUCKET}-{REGION}.s3.{REGION}.amazonaws.com/{SOLUTION_NAME}{branch_suffix}/deployment/sagemaker-purchase-modelling.yaml"<jupyter_output><empty_output><jupyter_text>In the next cell we create a unique prefix for our solution, and create an S3 bucket that will serve as the destination for the notebook files we run on the SM instance. It's important that its name starts with the solution prefix, as that will allow the solution itself to write to it (because the solution should have write access to all `sagemaker-soln-` buckets under the same account).<jupyter_code>import boto3
import uuid
import logging
import uuid
import os

logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))

cfn_client = boto3.client('cloudformation', region_name=REGION)
s3_client = boto3.client('s3', region_name=REGION)
s3 = boto3.resource('s3', region_name=REGION)

unique_id = uuid.uuid4().hex[:8]

# Use the commit id to give the solution a unique prefix and name
solution_prefix = "sagemaker-soln-pmdl-" # TODO: Get from template directly
unique_prefix = f"{solution_prefix}{unique_id}"<jupyter_output><empty_output><jupyter_text>The `TestOutputsS3Bucket` CloudFormation parameter given in the next cell, is parsed by CloudFormation and taken in by the project's configuration package (see `source/notebooks/src/package/config.py`). When this parameter is set to something different than `""`, the notebook instance will run the solution's notebook using papermill, through the instance's on-start script (see `deployment/sagemaker-purchase-modelling-notebook-instance.yaml`).<jupyter_code>logging.info(f"Creating stack using template located at {template_url}")
logging.info(f"STACK_NAME: {STACK_NAME}")
logging.info(f"REGION: {REGION}")
logging.info(f"SOLUTIONS_BUCKET: {SOLUTIONS_BUCKET}")
logging.info(f"CI_BUCKET: {CI_BUCKET}")
logging.info(f"StackVersion: {STACK_VERSION}")
logging.info(f"SolutionPrefix: {unique_prefix}")

cfn_client.create_stack(
    StackName=STACK_NAME,
    TemplateURL=template_url,
    Parameters=[
        {
            'ParameterKey': 'SolutionPrefix',
            'ParameterValue': unique_prefix
        },
        {
            'ParameterKey': 'StackVersion',
            'ParameterValue': STACK_VERSION
        },
        {
            'ParameterKey': 'TestOutputsS3Bucket',
            'ParameterValue': CI_BUCKET
        },
        {
            'ParameterKey': 'SolutionName',
            'ParameterValue': f"{SOLUTION_NAME}{branch_suffix}"
        }
    ],
    Capabilities=[
        'CAPABILITY_IAM',
        'CAPABILITY_NAMED_IAM'
    ]
)<jupyter_output><empty_output><jupyter_text>We then wait for the stack to finish launching.<jupyter_code>logging.info("Waiting for stack creation to complete...")
waiter = cfn_client.get_waiter('stack_create_complete')

waiter.wait(StackName=STACK_NAME)

logging.info("Stack creation complete, notebook run has begun...")

logging.info("Notebook instance run logs will be available at:")
logging.info(f"https://{REGION}.console.aws.amazon.com/cloudwatch/home?region=us-west-2#logsV2:log-groups/log-group/$252Faws$252Fsagemaker$252FNotebookInstances/log-events/{unique_prefix}-notebook-instance$252Frun-notebook.log")<jupyter_output><empty_output><jupyter_text>Once the stack has finished creating, the OnStart script will attempt to run the `sagemaker_fraud_detection.ipynb` notebook, through the `test/run_notebook.py` script. The notebook is run using papermill, and creates an output in the CI S3 bucket we created previously. The following cell will continuously poll the expected location until the output file appears, or errors out after `NOTEBOOK_POLL_DELAY * NOTEBOOK_POLL_ATTEMPTS` seconds. This also means that the CodeBuild project needs to be able to read files from the particular bucket.Note that if this is longer than the build stage's timeout, the build stage will fail. If your solution's notebooks take very long to run, make sure to [increase the build stage's time out](https://docs.aws.amazon.com/codebuild/latest/userguide/change-project-console.html) as well, can be set using a parameter in the CFT you used to create the pipeline.<jupyter_code># TODO: Ensure there's a single source for these filenames, either in the config, or passed as a papermill parameter?
# Right now they're set here and in run_notebook.py
import os
prefix = 'integration-test' 
key = "output.ipynb"



waiter = s3_client.get_waiter('object_exists')

logging.info(f"Waiting for output notebook to appear at {CI_BUCKET}/{os.path.join(prefix, key)}...")
logging.info(f"Will attempt a total {NOTEBOOK_POLL_ATTEMPTS} every {NOTEBOOK_POLL_DELAY} seconds.")
waiter.wait(Bucket=CI_BUCKET, Key=os.path.join(prefix, key), WaiterConfig={'Delay': NOTEBOOK_POLL_DELAY,'MaxAttempts': NOTEBOOK_POLL_ATTEMPTS})<jupyter_output><empty_output><jupyter_text>Once the notebook appears in the expected location in S3, we download it locally within the build instance, and the stdout and stderr output we got from running the notebook. This doesn't actually run the notebook, but will raise and surface any errors that we triggered during execution on the SM notebook instance. If your solution needs to run more than one notebook you would need to wait for each one to finish in the order you expect them to execute, download them, then dry-run them sequentially here.<jupyter_code># Dry-run execute the notebook, raising errors if any existed
import papermill as pm

logging.info("Downloading notebook outputs locally...")
s3.meta.client.download_file(CI_BUCKET, os.path.join(prefix, key), key)
try:
    s3.meta.client.download_file(CI_BUCKET, os.path.join(prefix, "output_stdout.txt"), "output_stdout.txt")
    s3.meta.client.download_file(CI_BUCKET, os.path.join(prefix, "output_stderr.txt"), "output_stderr.txt")
except:
    pass

# TODO: this notebook filename should also be a parameter
logging.info("Performing dry-run of notebooks to surface any errors...")
nb = pm.iorw.load_notebook_node(key)
pm.execute.raise_for_execution_errors(nb, key)

print("Test deployment and notebook execution completed successfully!")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08928809048569528, 'fraction_numerical': 0.005455755156353959, 'mean_word_length': 5.44044558697515, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 29, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '204955', 'license': 'Apache-2.0', 'n_tokens_mistral': 2253, 'n_tokens_neox': 2205, 'n_words': 727, 'path': 'test/test_deployment.ipynb', 'repo_name': 'awslabs/sagemaker-purchase-modelling', 'size': '7515'}