Exemplo n.º 1
0
async def save_and_deploy_resource(
        resource: Resource, resource_repo: ResourceRepository,
        operations_repo: OperationRepository,
        resource_template_repo: ResourceTemplateRepository, user: User,
        resource_template: ResourceTemplate) -> Operation:
    try:
        resource.user = user
        resource.updatedWhen = get_timestamp()
        resource_repo.save_item(resource)
    except Exception as e:
        logging.error(f'Failed saving resource item {resource}: {e}')
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                            detail=strings.STATE_STORE_ENDPOINT_NOT_RESPONDING)

    try:
        operation = await send_resource_request_message(
            resource=resource,
            operations_repo=operations_repo,
            resource_repo=resource_repo,
            user=user,
            resource_template=resource_template,
            resource_template_repo=resource_template_repo,
            action=RequestAction.Install)
        return operation
    except Exception as e:
        resource_repo.delete_item(resource.id)
        logging.error(f"Failed send resource request message: {e}")
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                            detail=strings.SERVICE_BUS_GENERAL_ERROR_MESSAGE)
Exemplo n.º 2
0
def try_upgrade(resource_repo: ResourceRepository, resource_template_repo: ResourceTemplateRepository, properties: dict, user: User, resource_to_update_id: str) -> Resource:
    resource_to_update = resource_repo.get_resource_by_id(resource_to_update_id)

    # get the template for the resource to upgrade
    parent_service_name = ""
    if resource_to_update.resourceType == ResourceType.UserResource:
        parent_service_name = resource_to_update["parentWorkspaceServiceId"]

    resource_template_to_send = resource_template_repo.get_current_template(resource_to_update.templateName, resource_to_update.resourceType, parent_service_name)

    # create the patch
    patch = ResourcePatch(
        properties=properties
    )

    # validate and submit the patch
    resource_to_send, _ = resource_repo.patch_resource(
        resource=resource_to_update,
        resource_patch=patch,
        resource_template=resource_template_to_send,
        etag=resource_to_update.etag,
        resource_template_repo=resource_template_repo,
        user=user)

    return resource_to_send
Exemplo n.º 3
0
 def build_step_list(self, steps: List[OperationStep],
                     resource_template_dict: dict, action: str,
                     resource_repo: ResourceRepository, resource_id: str,
                     status: Status, message: str):
     if "pipeline" in resource_template_dict and resource_template_dict[
             "pipeline"] is not None:
         if action in resource_template_dict[
                 "pipeline"] and resource_template_dict["pipeline"][
                     action] is not None:
             for step in resource_template_dict["pipeline"][action]:
                 if step["stepId"] == "main":
                     steps.append(
                         self.create_main_step(
                             resource_template=resource_template_dict,
                             action=action,
                             resource_id=resource_id,
                             status=status,
                             message=message))
                 else:
                     resource_for_step = resource_repo.get_resource_by_template_name(
                         step["resourceTemplateName"])
                     steps.append(
                         OperationStep(
                             stepId=step["stepId"],
                             stepTitle=step["stepTitle"],
                             resourceId=resource_for_step.id,
                             resourceTemplateName=step[
                                 "resourceTemplateName"],
                             resourceType=step["resourceType"],
                             resourceAction=step["resourceAction"],
                             updatedWhen=self.get_timestamp()))
     return steps
Exemplo n.º 4
0
def update_resource_for_step(operation_step: OperationStep, resource_repo: ResourceRepository, resource_template_repo: ResourceTemplateRepository, primary_resource_id: str, resource_to_update_id: str, primary_action: str, user: User) -> Resource:
    # create properties dict - for now we create a basic, string only dict to use as a patch
    # get primary resource to use in substitutions
    primary_resource = resource_repo.get_resource_by_id(primary_resource_id)

    # if this is main, just leave it alone and return it
    if operation_step.stepId == "main":
        return primary_resource

    # get the template for the primary resource, to get all the step details for substitutions
    primary_parent_service_name = ""
    if primary_resource.resourceType == ResourceType.UserResource:
        primary_parent_workspace_service = resource_repo.get_resource_by_id(primary_resource.parentWorkspaceServiceId)
        primary_parent_service_name = primary_parent_workspace_service.templateName
    primary_template = resource_template_repo.get_current_template(primary_resource.templateName, primary_resource.resourceType, primary_parent_service_name)

    # get the template step
    template_step = None
    for step in primary_template.pipeline.dict()[primary_action]:
        if step["stepId"] == operation_step.stepId:
            template_step = parse_obj_as(PipelineStep, step)
            break

    if template_step is None:
        raise f"Cannot find step with id of {operation_step.stepId} in template {primary_resource.templateName} for action {primary_action}"

    # TODO: actual substitution logic #1679
    properties = {}
    for prop in template_step.properties:
        properties[prop.name] = prop.value

    if template_step.resourceAction == "upgrade":
        resource_to_send = try_upgrade_with_retries(
            num_retries=3,
            attempt_count=0,
            resource_repo=resource_repo,
            resource_template_repo=resource_template_repo,
            properties=properties,
            user=user,
            resource_to_update_id=resource_to_update_id
        )

        return resource_to_send

    else:
        raise Exception("Only upgrade is currently supported for pipeline steps")
async def receive_message_and_update_deployment(app) -> None:
    """
    Receives messages from the deployment status update queue and updates the status for
    the associated resource in the state store.
    Args:
        app ([FastAPI]): Handle to the currently running app
    """
    receive_message_gen = receive_message()

    try:
        async for message in receive_message_gen:
            operations_repo = OperationRepository(get_db_client(app))
            resource_repo = ResourceRepository(get_db_client(app))
            resource_template_repo = ResourceTemplateRepository(
                get_db_client(app))
            result = await update_status_in_database(resource_repo,
                                                     operations_repo,
                                                     resource_template_repo,
                                                     message)
            await receive_message_gen.asend(result)
    except StopAsyncIteration:  # the async generator when finished signals end with this exception.
        pass
Exemplo n.º 6
0
def resource_repo():
    with patch('azure.cosmos.CosmosClient') as cosmos_client_mock:
        yield ResourceRepository(cosmos_client_mock)
async def update_status_in_database(
        resource_repo: ResourceRepository,
        operations_repo: OperationRepository,
        resource_template_repo: ResourceTemplateRepository,
        message: DeploymentStatusUpdateMessage):
    """
    Get the operation the message references, and find the step within the operation that is to be updated
    Update the status of the step. If it's a single step operation, copy the status into the operation status. If it's a multi step,
    update the step and set the overall status to "pipeline_deploying".
    If there is another step in the operation after this one, process the substitutions + patch, then enqueue a message to process it.
    """
    result = False

    try:
        # update the op
        operation = operations_repo.get_operation_by_id(
            str(message.operationId))
        step_to_update = None
        is_last_step = False

        current_step_index = 0
        for i, step in enumerate(operation.steps):
            if step.stepId == message.stepId:
                step_to_update = step
                current_step_index = i
                if i == (len(operation.steps) - 1):
                    is_last_step = True

        if step_to_update is None:
            raise f"Error finding step {message.stepId} in operation {message.operationId}"

        # update the step status
        update_step_status(step_to_update, message)

        # update the overall headline operation status
        update_overall_operation_status(operation, step_to_update,
                                        is_last_step)

        # save the operation
        operations_repo.update_item(operation)

        # if the step failed, or this queue message is an intermediary ("now deploying..."), return here.
        if not step_to_update.is_success():
            return True

        # update the resource doc to persist any outputs
        resource = resource_repo.get_resource_dict_by_id(
            uuid.UUID(step_to_update.resourceId))
        resource_to_persist = create_updated_resource_document(
            resource, message)
        resource_repo.update_item_dict(resource_to_persist)

        # more steps in the op to do?
        if is_last_step is False:
            assert current_step_index < (len(operation.steps) - 1)
            next_step = operation.steps[current_step_index + 1]

            resource_to_send = update_resource_for_step(
                operation_step=next_step,
                resource_repo=resource_repo,
                resource_template_repo=resource_template_repo,
                primary_resource_id=operation.resourceId,
                resource_to_update_id=next_step.resourceId,
                primary_action=operation.action,
                user=operation.user)

            # create + send the message
            logging.info(
                f"Sending next step in operation to deployment queue -> step_id: {next_step.stepId}, action: {next_step.resourceAction}"
            )
            content = json.dumps(
                resource_to_send.get_resource_request_message_payload(
                    operation_id=operation.id,
                    step_id=next_step.stepId,
                    action=next_step.resourceAction))
            await send_deployment_message(content=content,
                                          correlation_id=operation.id,
                                          session_id=resource_to_send.id,
                                          action=next_step.resourceAction)

        result = True

    except EntityDoesNotExist:
        # Marking as true as this message will never succeed anyways and should be removed from the queue.
        result = True
        error_string = strings.DEPLOYMENT_STATUS_ID_NOT_FOUND.format(
            message.id)
        logging.error(error_string)
    except Exception as e:
        logging.error(strings.STATE_STORE_ENDPOINT_NOT_RESPONDING + " " +
                      str(e))

    return result
Exemplo n.º 8
0
def resource_repo() -> ResourceRepository:
    with patch("azure.cosmos.CosmosClient") as cosmos_client_mock:
        return ResourceRepository(cosmos_client_mock)