コード例 #1
0
def change_plan_resolve_conflict(request, id):
    """
    Resolve a merge conflict
    """
    (change_plan, failure_response) = get_change_plan(id)
    if failure_response:
        return failure_response
    failure_response = get_cp_already_executed_response(change_plan)
    if failure_response:
        return failure_response
    data = JSONParser().parse(request)
    if "asset_cp" not in data or "override_live" not in data:
        return JsonResponse(
            {
                "failure_message": Status.ERROR.value + GenericFailure.INTERNAL.value,
                "errors": "Must include both 'asset_cp' and `override_live` when resolving a merge conflict",
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    asset_cp = data["asset_cp"]
    override_live = data["override_live"]
    try:
        asset_cp = AssetCP.objects.get(id=asset_cp)
    except ObjectDoesNotExist:
        return JsonResponse(
            {
                "failure_message": Status.ERROR.value
                + "AssetCP"
                + GenericFailure.DOES_NOT_EXIST.value,
                "errors": "No existing Asset CP with id=" + str(asset_cp),
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    try:
        if override_live:
            asset_cp.is_conflict = False
            asset_cp.save()
        else:
            asset_cp.delete()
    except Exception as error:
        return JsonResponse(
            {
                "failure_message": Status.DELETE_ERROR.value
                + "Change Plan"
                + GenericFailure.ON_DELETE.value,
                "errors": str(error),
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    return JsonResponse(
        {"success_message": Status.SUCCESS.value + "Sucessfully resolved conflict "},
        status=HTTPStatus.OK,
    )
コード例 #2
0
def change_plan_modify(request):
    """
    Modify single existing change plan
    """
    data = JSONParser().parse(request)
    if "id" not in data:
        return JsonResponse(
            {
                "failure_message": Status.MODIFY_ERROR.value
                + GenericFailure.INTERNAL.value,
                "errors": "Must include 'id' when modifying a change plan",
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    id = data["id"]
    (existing_change_plan, failure_response) = get_change_plan(id)
    if failure_response:
        return failure_response
    failure_response = get_cp_already_executed_response(existing_change_plan)
    if failure_response:
        return failure_response
    for field in data.keys():
        value = data[field]
        setattr(existing_change_plan, field, value)
    try:
        existing_change_plan.save()
        return JsonResponse(
            {
                "success_message": Status.SUCCESS.value
                + "Change Plan "
                + str(existing_change_plan.name)
                + " modified",
                "related_id": str(existing_change_plan.id),
            },
            status=HTTPStatus.OK,
        )
    except Exception as error:
        return JsonResponse(
            {
                "failure_message": Status.MODIFY_ERROR.value
                + parse_save_validation_error(error, "Asset"),
                "errors": str(error),
            },
            status=HTTPStatus.BAD_REQUEST,
        )
コード例 #3
0
def change_plan_delete(request):
    """
    Delete a single existing change plan
    """
    data = JSONParser().parse(request)
    if "id" not in data:
        return JsonResponse(
            {
                "failure_message": Status.DELETE_ERROR.value
                + GenericFailure.INTERNAL.value,
                "errors": "Must include 'id' when deleting a Change Plan",
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    (existing_change_plan, failure_response) = get_change_plan(data["id"])
    if failure_response:
        return failure_response
    failure_response = get_cp_already_executed_response(existing_change_plan)
    if failure_response:
        return failure_response
    try:
        existing_change_plan.delete()
    except Exception as error:
        return JsonResponse(
            {
                "failure_message": Status.DELETE_ERROR.value
                + "Change Plan"
                + GenericFailure.ON_DELETE.value,
                "errors": str(error),
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    return JsonResponse(
        {
            "success_message": Status.SUCCESS.value
            + "Change Plan "
            + str(existing_change_plan.name)
            + " deleted"
        },
        status=HTTPStatus.OK,
    )
コード例 #4
0
def asset_modify(request):
    """
    Modify a single existing asset
    """
    data = JSONParser().parse(request)
    if "id" not in data:
        return JsonResponse(
            {
                "failure_message": Status.MODIFY_ERROR.value
                + GenericFailure.INTERNAL.value,
                "errors": "Must include 'id' when modifying an asset",
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    asset_id = data["id"]

    (change_plan, failure_response) = get_change_plan(
        request.query_params.get("change_plan")
    )
    if failure_response:
        return failure_response
    create_new_asset_cp = False
    existing_asset = None
    if change_plan:
        failure_response = get_cp_already_executed_response(change_plan)
        if failure_response:
            return failure_response
        if not does_asset_exist(asset_id, change_plan):
            return JsonResponse(
                {
                    "failure_message": Status.MODIFY_ERROR.value
                    + "Asset"
                    + GenericFailure.DOES_NOT_EXIST.value,
                    "errors": "No existing asset with id=" + str(asset_id),
                },
                status=HTTPStatus.BAD_REQUEST,
            )
        try:
            existing_asset = AssetCP.objects.get(
                id=asset_id, change_plan=change_plan.id
            )
        except ObjectDoesNotExist:
            create_new_asset_cp = True

    if create_new_asset_cp or not change_plan:
        try:
            existing_asset = Asset.objects.get(id=asset_id)
        except ObjectDoesNotExist:
            return JsonResponse(
                {
                    "failure_message": Status.MODIFY_ERROR.value
                    + "Model"
                    + GenericFailure.DOES_NOT_EXIST.value,
                    "errors": "No existing asset with id=" + str(asset_id),
                },
                status=HTTPStatus.BAD_REQUEST,
            )
    try:
        validate_user_permission_on_existing_asset(request.user, existing_asset)
    except UserAssetPermissionException as auth_error:
        return JsonResponse(
            {"failure_message": Status.AUTH_ERROR.value + str(auth_error)},
            status=HTTPStatus.UNAUTHORIZED,
        )

    try:
        validate_user_permission_on_new_asset_data(
            request.user, data, data_is_validated=False, change_plan=change_plan
        )
    except UserAssetPermissionException as auth_error:
        return JsonResponse(
            {"failure_message": Status.AUTH_ERROR.value + str(auth_error)},
            status=HTTPStatus.UNAUTHORIZED,
        )
    try:
        validate_hostname_deletion(data, existing_asset)
    except AssetModificationException as modifcation_exception:
        return JsonResponse(
            {
                "failure_message": Status.MODIFY_ERROR.value
                + "Invalid hostname deletion. "
                + str(modifcation_exception)
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    try:
        validate_location_modification(data, existing_asset, change_plan=change_plan)
    except Exception as error:
        return JsonResponse(
            {
                "failure_message": Status.MODIFY_ERROR.value
                + "Invalid location change. "
                + str(error)
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    asset_cp = None
    if change_plan:
        (asset_cp, failure_message) = save_all_field_data_cp(
            data, existing_asset, change_plan, create_new_asset_cp
        )
    else:
        failure_message = save_all_field_data_live(data, existing_asset)
    if failure_message:
        return JsonResponse(
            {"failure_message": Status.MODIFY_ERROR.value + failure_message},
            status=HTTPStatus.BAD_REQUEST,
        )

    if asset_cp:
        existing_asset = asset_cp

    warning_message = save_all_connection_data(
        data, existing_asset, request.user, change_plan=change_plan
    )
    if warning_message:
        return JsonResponse({"warning_message": warning_message}, status=HTTPStatus.OK)

    if change_plan:
        return JsonResponse(
            {
                "success_message": Status.SUCCESS.value
                + "Asset modified on change plan "
                + change_plan.name,
                "related_id": change_plan.id,
            },
            status=HTTPStatus.OK,
        )
    else:
        log_action(request.user, existing_asset, Action.MODIFY)
        return JsonResponse(
            {
                "success_message": Status.SUCCESS.value
                + "Asset "
                + str(existing_asset.asset_number)
                + " modified"
            },
            status=HTTPStatus.OK,
        )
コード例 #5
0
def asset_add(request):
    """
    Add a new asset.
    """
    data = JSONParser().parse(request)
    if "id" in data:
        return JsonResponse(
            {
                "failure_message": Status.CREATE_ERROR.value
                + GenericFailure.INTERNAL.value,
                "errors": "Don't include 'id' when creating an asset",
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    (change_plan, failure_response) = get_change_plan(
        request.query_params.get("change_plan")
    )
    if failure_response:
        return failure_response
    chassis_id_live = None
    if change_plan:
        failure_response = get_cp_already_executed_response(change_plan)
        if failure_response:
            return failure_response
        data["change_plan"] = change_plan.id

        if data["chassis"] and not AssetCP.objects.filter(id=data["chassis"]).exists():
            # ignore the chassis because we will replace it with a new chassis on AssetCP later
            chassis_id_live = data["chassis"]
            del data["chassis"]

        serializer = AssetCPSerializer(data=data)
    else:
        serializer = AssetSerializer(data=data)
    if not serializer.is_valid(raise_exception=False):
        return JsonResponse(
            {
                "failure_message": Status.INVALID_INPUT.value
                + parse_serializer_errors(serializer.errors),
                "errors": str(serializer.errors),
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    try:
        validate_user_permission_on_new_asset_data(
            request.user,
            serializer.validated_data,
            data_is_validated=True,
            change_plan=change_plan,
            chassis_id_live=chassis_id_live,
        )
    except UserAssetPermissionException as auth_error:
        return JsonResponse(
            {"failure_message": Status.AUTH_ERROR.value + str(auth_error)},
            status=HTTPStatus.UNAUTHORIZED,
        )
    except Exception as error:
        return JsonResponse(
            {"failure_message": Status.CREATE_ERROR.value + str(error)},
            status=HTTPStatus.BAD_REQUEST,
        )
    if not (
        "offline_storage_site" in serializer.validated_data
        and serializer.validated_data["offline_storage_site"]
    ):
        if serializer.validated_data["model"].is_rackmount():
            if (
                "rack" not in serializer.validated_data
                or not serializer.validated_data["rack"]
                or "rack_position" not in serializer.validated_data
                or not serializer.validated_data["rack_position"]
            ):
                return JsonResponse(
                    {
                        "failure_message": Status.INVALID_INPUT.value
                        + "Must include rack and rack position to add a rackmount asset. "
                    },
                    status=HTTPStatus.BAD_REQUEST,
                )
            rack_id = serializer.validated_data["rack"].id
            rack_position = serializer.validated_data["rack_position"]
            height = serializer.validated_data["model"].height
            try:
                validate_asset_location_in_rack(
                    rack_id, rack_position, height, change_plan=change_plan
                )
            except LocationException as error:
                return JsonResponse(
                    {"failure_message": Status.CREATE_ERROR.value + str(error)},
                    status=HTTPStatus.BAD_REQUEST,
                )
        else:
            if (
                (
                    (
                        "chassis" not in serializer.validated_data
                        or not serializer.validated_data["chassis"]
                    )
                    and not chassis_id_live
                )
                or "chassis_slot" not in serializer.validated_data
                or not serializer.validated_data["chassis_slot"]
            ):
                return JsonResponse(
                    {
                        "failure_message": Status.INVALID_INPUT.value
                        + "Must include chassis and chassis slot to add a blade asset. "
                    },
                    status=HTTPStatus.BAD_REQUEST,
                )

            if chassis_id_live:
                try:
                    chassis_live = Asset.objects.get(id=chassis_id_live)
                except ObjectDoesNotExist:
                    return JsonResponse(
                        {
                            "failure_message": Status.MODIFY_ERROR.value
                            + "Chassis"
                            + GenericFailure.DOES_NOT_EXIST.value,
                            "errors": "No existing chassis with id="
                            + str(chassis_id_live),
                        },
                        status=HTTPStatus.BAD_REQUEST,
                    )
                chassis_cp = add_chassis_to_cp(chassis_live, change_plan)
                chassis_id = chassis_cp.id
                serializer.validated_data["chassis"] = chassis_cp

            else:
                chassis_id = serializer.validated_data["chassis"].id
            chassis_slot = serializer.validated_data["chassis_slot"]
            try:
                validate_asset_location_in_chassis(
                    chassis_id, chassis_slot, change_plan=change_plan
                )
            except LocationException as error:
                return JsonResponse(
                    {"failure_message": Status.CREATE_ERROR.value + str(error)},
                    status=HTTPStatus.BAD_REQUEST,
                )
    try:
        asset = serializer.save()
    except Exception as error:
        return JsonResponse(
            {
                "failure_message": Status.CREATE_ERROR.value
                + parse_save_validation_error(error, "Asset"),
                "errors": str(error),
            },
            status=HTTPStatus.BAD_REQUEST,
        )

    warning_message = save_all_connection_data(
        data, asset, request.user, change_plan=change_plan
    )
    if warning_message:
        return JsonResponse({"warning_message": warning_message}, status=HTTPStatus.OK)
    if change_plan:
        return JsonResponse(
            {
                "success_message": Status.SUCCESS.value
                + "Asset created on change plan "
                + change_plan.name,
                "related_id": change_plan.id,
            },
            status=HTTPStatus.OK,
        )
    else:
        log_action(request.user, asset, Action.CREATE)
        return JsonResponse(
            {
                "success_message": Status.SUCCESS.value
                + "Asset "
                + str(asset.asset_number)
                + " created"
            },
            status=HTTPStatus.OK,
        )
コード例 #6
0
def change_plan_execute(request, id):
    """
    Execute all changes associated with a change plan.
    """
    (change_plan, failure_response) = get_change_plan(id)
    if failure_response:
        return failure_response
    failure_response = get_cp_already_executed_response(change_plan)
    if failure_response:
        return failure_response
    if request.user != change_plan.owner:
        return JsonResponse(
            {
                "failure_message": Status.ERROR.value
                + "You do not have access to execute this change plan.",
                "errors": "User "
                + request.user.username
                + " does not own change plan with id="
                + str(id),
            },
            status=HTTPStatus.BAD_REQUEST,
        )

    assets_cp = AssetCP.objects.filter(change_plan=change_plan)
    for asset_cp in assets_cp:
        if get_cp_modification_conflicts(asset_cp):
            return JsonResponse(
                {
                    "failure_message": Status.ERROR.value
                    + "All conflicts must be resolved before a change "
                    + "plan can be executed.",
                    "errors": "Conflict found on AssetCP with id=" + str(asset_cp.id),
                },
                status=HTTPStatus.BAD_REQUEST,
            )

    change_plan.execution_time = datetime.now()
    change_plan.save()

    num_created = 0
    num_modified = 0
    num_decommissioned = 0
    updated_asset_mappings = {}

    # rackmount assets first
    rackmount_assets_cp = assets_cp.filter(
        ~Q(model__model_type=ModelType.BLADE_ASSET.value)
    )
    for asset_cp in rackmount_assets_cp:
        changes = []
        if asset_cp.related_asset:
            changes = get_changes_on_asset(asset_cp.related_asset, asset_cp)
        updated_asset, created = get_updated_asset(asset_cp)
        updated_asset_mappings[asset_cp] = updated_asset
        differs_from_live = True

        if created and not asset_cp.is_decommissioned:
            num_created += 1
            log_action(
                request.user, updated_asset, Action.CREATE, change_plan=change_plan,
            )
        elif not asset_cp.is_decommissioned:
            if len(changes) > 0:
                num_modified += 1
                log_action(
                    request.user, updated_asset, Action.MODIFY, change_plan=change_plan,
                )
            else:
                differs_from_live = False
        asset_cp.differs_from_live = differs_from_live
        asset_cp.save()

        update_network_ports(updated_asset, asset_cp, change_plan)
        update_power_ports(updated_asset, asset_cp, change_plan)
    ##blade assets
    blade_assets_cp = assets_cp.filter(model__model_type=ModelType.BLADE_ASSET.value)
    for asset_cp in blade_assets_cp:
        changes = []
        if asset_cp.related_asset:
            changes = get_changes_on_asset(asset_cp.related_asset, asset_cp)
        chassis_live = updated_asset_mappings[asset_cp.chassis]
        updated_asset, created = get_updated_asset(asset_cp, chassis_live)
        updated_asset_mappings[asset_cp] = updated_asset
        differs_from_live = True

        if created:
            num_created += 1
            log_action(
                request.user, updated_asset, Action.CREATE, change_plan=change_plan,
            )
        elif not asset_cp.is_decommissioned:
            if len(changes) > 0:
                num_modified += 1
                log_action(
                    request.user, updated_asset, Action.MODIFY, change_plan=change_plan,
                )
            else:
                differs_from_live = False
        asset_cp.differs_from_live = differs_from_live
        asset_cp.save()
        update_network_ports(updated_asset, asset_cp, change_plan)
        update_power_ports(updated_asset, asset_cp, change_plan)
        updated_asset.save()
    ## decomission blades first
    blades_cp = assets_cp.filter(model__model_type=ModelType.BLADE_ASSET.value)
    rackmount_cp = assets_cp.filter(~Q(model__model_type=ModelType.BLADE_ASSET.value))
    for asset_cp_query in [blades_cp, rackmount_cp]:
        for asset_cp in asset_cp_query:
            # Decommission only after all changes have been made to all CP assets
            updated_asset = updated_asset_mappings[asset_cp]
            if asset_cp.is_decommissioned:
                failure_response = decommission_asset_cp(
                    updated_asset, asset_cp, change_plan,
                )
                if failure_response:
                    return failure_response
                num_decommissioned += 1
                log_action(
                    request.user, None, Action.DECOMMISSION, change_plan=change_plan,
                )

    log_execute_change_plan(
        request.user, change_plan.name, num_created, num_modified, num_decommissioned,
    )

    return JsonResponse(
        {
            "success_message": "Change Plan '"
            + change_plan.name
            + "' executed: "
            + str(num_created)
            + " assets created, "
            + str(num_modified)
            + " assets modified, "
            + str(num_decommissioned)
            + " assets decommissioned."
        },
        status=HTTPStatus.OK,
    )
コード例 #7
0
def change_plan_remove_asset(request, id):
    """
    Remove a single assetCP from a change plan
    """
    (change_plan, failure_response) = get_change_plan(id)
    if failure_response:
        return failure_response
    failure_response = get_cp_already_executed_response(change_plan)
    if failure_response:
        return failure_response
    data = JSONParser().parse(request)
    if "asset_cp" not in data:
        return JsonResponse(
            {
                "failure_message": Status.ERROR.value + GenericFailure.INTERNAL.value,
                "errors": "Must include 'asset_cp' when removing an asset from change plan",
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    asset_cp = data["asset_cp"]

    try:
        asset_cp_object = AssetCP.objects.get(id=asset_cp)
    except ObjectDoesNotExist:
        return JsonResponse(
            {
                "failure_message": Status.ERROR.value
                + "AssetCP"
                + GenericFailure.DOES_NOT_EXIST.value,
                "errors": "No existing Asset CP with id=" + str(asset_cp),
            },
            status=HTTPStatus.BAD_REQUEST,
        )
    try:
        if asset_cp_object.model.is_blade_chassis():
            blades = AssetCP.objects.filter(chassis=asset_cp_object, change_plan=change_plan)
            remove_chassis = True
            for blade in blades:
                if len(get_changes_on_asset(blade.related_asset, blade)) == 0:
                    remove_chassis = False
            if remove_chassis:
                asset_cp_object.delete()
            else:
                for field in asset_cp.related_asset._meta.fields:
                    if (
                            field.name != "id"
                            and field.name != "assetid_ptr"
                            and field.name != "chassis"
                    ):
                        setattr(asset_cp, field.name, getattr(asset_cp.related_asset, field.name))

        else:
            asset_cp_object.delete()
    except Exception as error:
        return JsonResponse(
            {
                "failure_message": Status.DELETE_ERROR.value
                + "Change Plan"
                + GenericFailure.ON_DELETE.value,
                "errors": str(error),
            },
            status=HTTPStatus.BAD_REQUEST,
        )

    return JsonResponse(
        {
            "success_message": Status.SUCCESS.value
            + "Asset successfully removed from change plan"
        },
        status=HTTPStatus.OK,
    )
コード例 #8
0
def decommission_asset_parameterized(asset_id, query_params, user,
                                     change_plan):
    """
    Decommission a live asset
    """
    decommissioned_asset_cp = None
    if change_plan:
        response = get_cp_already_executed_response(change_plan)
        if response:
            return None, response
        assets, assets_cp = get_assets_for_cp(change_plan.id,
                                              show_decommissioned=True)
        if assets_cp.filter(related_asset=asset_id).exists():

            decommissioned_asset_cp = assets_cp.get(related_asset=asset_id)
            decommissioned_asset_cp.is_decommissioned = True
            asset_id = decommissioned_asset_cp.id
        elif assets_cp.filter(id=asset_id).exists():

            decommissioned_asset_cp = assets_cp.get(id=asset_id)
            decommissioned_asset_cp.is_decommissioned = True
            asset_id = decommissioned_asset_cp.id
        elif assets.filter(id=asset_id).exists():

            existing_asset = assets.get(id=asset_id)

            if existing_asset.model.is_blade_chassis():
                decommissioned_asset_cp = add_chassis_to_cp(
                    existing_asset, change_plan)

            else:
                chassis_cp = None
                if existing_asset.model.is_blade_asset(
                ) and existing_asset.chassis:
                    if not assets_cp.filter(
                            related_asset=existing_asset.chassis.id).exists():
                        chassis_cp = add_chassis_to_cp(
                            existing_asset.chassis,
                            change_plan,
                            ignore_blade_id=existing_asset.id,
                        )
                    else:
                        chassis_cp = assets_cp.get(
                            related_asset=existing_asset.chassis.id)

                decommissioned_asset_cp = copy_asset_to_new_asset_cp(
                    existing_asset, change_plan, chassis_cp=chassis_cp)
            decommissioned_asset_cp.is_decommissioned = True
            decommissioned_asset_cp.save()

        else:
            return (
                None,
                JsonResponse(
                    {
                        "failure_message":
                        Status.ERROR.value + "Asset" +
                        GenericFailure.DOES_NOT_EXIST.value,
                        "errors":
                        "No existing asset in change plan with id=" +
                        str(asset_id),
                    },
                    status=HTTPStatus.BAD_REQUEST,
                ),
            )
        try:
            validate_user_permission_on_existing_asset(
                user, decommissioned_asset_cp)
        except UserAssetPermissionException as auth_error:
            return (
                None,
                JsonResponse(
                    {
                        "failure_message":
                        Status.AUTH_ERROR.value + str(auth_error)
                    },
                    status=HTTPStatus.UNAUTHORIZED,
                ),
            )
        if asset_id != decommissioned_asset_cp.id:
            ## a new asset cp was created for decommissioning, blades are not on assetxp
            blades = assets.filter(chassis=asset_id)
        else:
            blades = assets_cp.filter(chassis=asset_id)
        for blade in blades:
            try:
                decommission_asset_parameterized(blade.id, query_params, user,
                                                 change_plan)
                # Assume that if this call doesn't raise an exception, it was successful
                # None of the failed responses above are possible
            except Exception as error:

                return (
                    None,
                    JsonResponse(
                        {
                            "failure_message":
                            Status.DECOMMISSION_ERROR.value +
                            "Unable to decommission blade: '" +
                            str(blade.asset_number) + "'. ",
                            "errors":
                            str(error),
                        },
                        status=HTTPStatus.BAD_REQUEST,
                    ),
                )
        try:
            decommissioned_asset_cp.save()

        except Exception as error:

            return (
                None,
                JsonResponse(
                    {
                        "failure_message":
                        Status.DECOMMISSION_ERROR.value +
                        parse_save_validation_error(error,
                                                    "Decommissioned Asset "),
                        "errors":
                        str(error),
                    },
                    status=HTTPStatus.BAD_REQUEST,
                ),
            )

        return (
            JsonResponse(
                {
                    "success_message":
                    "Asset successfully decommissioned on change plan: " +
                    change_plan.name
                },
                status=HTTPStatus.OK,
            ),
            None,
        )

    try:
        asset = Asset.objects.get(id=asset_id)
    except Asset.DoesNotExist:
        return (
            None,
            JsonResponse(
                {
                    "failure_message":
                    Status.ERROR.value + "Asset" +
                    GenericFailure.DOES_NOT_EXIST.value,
                    "errors":
                    "No existing asset with id=" + str(asset_id),
                },
                status=HTTPStatus.BAD_REQUEST,
            ),
        )
    try:
        validate_user_permission_on_existing_asset(user, asset)
    except UserAssetPermissionException as auth_error:
        return (
            None,
            JsonResponse(
                {"failure_message": Status.AUTH_ERROR.value + str(auth_error)},
                status=HTTPStatus.UNAUTHORIZED,
            ),
        )

    blades = Asset.objects.filter(chassis=asset.id)
    for blade in blades:
        try:
            decommission_asset_parameterized(blade.id, query_params, user,
                                             change_plan)
            # Assume that if this call doesn't raise an exception, it was successful
            # None of the failed responses above are possible
        except Exception as error:
            return (
                None,
                JsonResponse(
                    {
                        "failure_message":
                        Status.DECOMMISSION_ERROR.value +
                        "Unable to decommission blade: '" +
                        str(blade.asset_number) + "'. ",
                        "errors":
                        str(error),
                    },
                    status=HTTPStatus.BAD_REQUEST,
                ),
            )
    asset_data = RecursiveAssetSerializer(asset).data
    asset_data["live_id"] = asset_data["id"]
    del asset_data["id"]
    asset_data["decommissioning_user"] = str(user)
    decommissioned_asset = AddDecommissionedAssetSerializer(data=asset_data)
    if not decommissioned_asset.is_valid(raise_exception=False):
        return (
            None,
            JsonResponse(
                {
                    "failure_message":
                    Status.INVALID_INPUT.value +
                    parse_serializer_errors(decommissioned_asset.errors),
                    "errors":
                    str(decommissioned_asset.errors),
                },
                status=HTTPStatus.BAD_REQUEST,
            ),
        )
    try:
        decommissioned_asset_object = decommissioned_asset.save()
    except Exception as error:
        return (
            None,
            JsonResponse(
                {
                    "failure_message":
                    Status.DECOMMISSION_ERROR.value +
                    parse_save_validation_error(error,
                                                "Decommissioned Asset "),
                    "errors":
                    str(error),
                },
                status=HTTPStatus.BAD_REQUEST,
            ),
        )
    else:
        for assetcp in AssetCP.objects.filter(related_asset=asset_id):
            assetcp.related_decommissioned_asset = decommissioned_asset_object
            assetcp.save()
        log_action(
            user,
            asset,
            Action.DECOMMISSION,
        )
        return (
            JsonResponse(
                {"success_message": "Asset successfully decommissioned. "},
                status=HTTPStatus.OK,
            ),
            None,
        )