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, )
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, )
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, )
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, )
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, )
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, )
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, )
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, )