using Coscine.Api.Resources.ParameterObjects; using Coscine.ApiCommons; using Coscine.Database.DataModel; using Coscine.Database.Models; using Coscine.Database.ReturnObjects; using Coscine.Database.Util; using Coscine.Logging; using Coscine.ResourceTypes; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; namespace Coscine.Api.Resources.Controllers { /// <summary> /// This controller represents the actions which can be taken with a resource object. /// </summary> [Authorize] public class ResourceQuotaController : Controller { private readonly Authenticator _authenticator; private readonly CoscineLogger _coscineLogger; private readonly ResourceModel _resourceModel; private readonly ProjectModel _projectModel; private readonly ProjectRoleModel _projectRoleModel; private readonly ProjectQuotaModel _projectQuotaModel; private readonly ProjectResourceModel _projectResourceModel; private readonly ResourceTypeModel _resourceTypeModel; private readonly float _oneGiB = 1024 * 1024 * 1024; // 1 GiB = 1024 Bytes /// <summary> /// ResourceQuotaController constructor specifying a ResourceModel. /// </summary> public ResourceQuotaController(ILogger<ResourceQuotaController> logger) { _authenticator = new Authenticator(this, Program.Configuration); _coscineLogger = new CoscineLogger(logger); _resourceModel = new ResourceModel(); _projectModel = new ProjectModel(); _projectRoleModel = new ProjectRoleModel(); _projectQuotaModel = new ProjectQuotaModel(); _projectResourceModel = new ProjectResourceModel(); _resourceTypeModel = new ResourceTypeModel(); } /// <summary> /// Retrieves the resource used and reserved quota for an individual resource. /// </summary> /// <param name="id">Id of the resource.</param> /// <returns>The quota of the resource.</returns> [HttpGet("[controller]/{id}")] public ActionResult<ResourceQuotaReturnObject> GetResourceQuota(Guid id) { var resource = _resourceModel.GetById(id); if (resource == null) { return base.NotFound($"Could not find resource with id: {id}"); } var projectId = _projectResourceModel.GetWhere(x => x.ResourceId == id).ProjectId; // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md) // - Resource: View Resource (RCV, Metadatamanager) if (!_projectModel.HasAccess(_authenticator.GetUser(), projectId, UserRoles.Owner, UserRoles.Member, UserRoles.Guest)) { return Unauthorized("The user is not authorized to perform a get on the selected project!"); } var displayName = _resourceTypeModel.GetById(resource.TypeId).DisplayName; var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resource); if (resourceTypeDefinition == null) { return BadRequest($"No provider for: \"{displayName}\"."); } return Json(Helpers.CreateResourceQuotaReturnObject(resource, resourceTypeDefinition)); } /// <summary> /// Updates the resource quota for an individual resource. /// </summary> /// <param name="id">Id of the resource.</param> /// <param name="updateResourceObject">Contains the resource id and the new quota.</param> /// <returns>204 if successful.</returns> [HttpPost("[controller]/{id}")] public IActionResult UpdateResourceQuota(Guid id, [FromBody] UpdateResourceObject updateResourceObject) { var resource = _resourceModel.GetById(id); if (resource == null) { return NotFound($"Could not find resource with id: {id}"); } var projectId = _projectResourceModel.GetWhere(x => x.ResourceId == id).ProjectId; var user = _authenticator.GetUser(); // Rights Matrix (https://git.rwth-aachen.de/coscine/docs/private/internal-wiki/-/blob/master/coscine/Definition%20of%20rights%20Matrix.md) // - Project: Change Settings (project, user, quota) if (!_projectModel.HasAccess(user, projectId, UserRoles.Owner) && resource.Creator != _authenticator.GetUser().Id) { return Unauthorized("The user is not authorized to perform a get on the selected project!"); } var resourceType = _resourceTypeModel.GetById(resource.TypeId); if (resourceType == null) { return NotFound($"Could not find resourceType with id: {resource.TypeId}"); } var resourceTypeDefinition = ResourceTypeFactory.Instance.GetResourceType(resource); if (resourceTypeDefinition == null) { return BadRequest($"No provider for: \"{resource.Type.DisplayName}\"."); } if (updateResourceObject.ReservedGiB < 1) { return BadRequest($"Allocated {updateResourceObject.ReservedGiB}. Cannot be less than 1."); } var projectQuota = _projectQuotaModel.GetWhere((x) => x.ProjectId == projectId && x.ResourceTypeId == resource.TypeId); var totalReserved = Helpers.CalculateTotalReservedQuota(resourceType, projectId); var reservedForCurrent = resourceTypeDefinition.GetResourceQuotaAvailable(resource.Id.ToString()).Result; var maximum = projectQuota.Quota - (totalReserved.Value - reservedForCurrent); if (totalReserved.Value - reservedForCurrent + updateResourceObject.ReservedGiB > projectQuota.Quota) { return BadRequest($"Cannot set quota to {updateResourceObject.ReservedGiB} GB. Current resource quota cannot reserve more than {maximum} GB. To increase that number, allocate more {resourceType.SpecificType} quota to the project."); } var used = (int)Math.Ceiling(resourceTypeDefinition.GetResourceQuotaUsed(resource.Id.ToString()).Result / _oneGiB); if (updateResourceObject.ReservedGiB < used) { return BadRequest($"Cannot set quota to {updateResourceObject.ReservedGiB} GB. Currently {used} GB are in use. You are not allowed to reserve less than what is currently in use."); } resourceTypeDefinition.SetResourceQuota(resource.Id.ToString(), updateResourceObject.ReservedGiB).Wait(); if (resourceType.Type == "rds") { var rdsResourceTypeModel = new RDSResourceTypeModel(); var rdsResourceType = rdsResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value); rdsResourceTypeModel.Update(rdsResourceType); } else if (resourceType.Type == "rdss3") { var rdsS3ResourceTypeModel = new RdsS3ResourceTypeModel(); var rdsS3ResourceType = rdsS3ResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value); rdsS3ResourceTypeModel.Update(rdsS3ResourceType); } if (Request.Query != null && Request.Query["noanalyticslog"] != "true") { LogAnalyticsOwnerResourceQuotaChange(resource, user, updateResourceObject.ReservedGiB, (int)maximum); } return NoContent(); } private void LogAnalyticsOwnerResourceQuotaChange(Resource resource, User user, int reserved, int maximum) { var resourceTypeDisplayName = _resourceTypeModel.GetById(resource.TypeId).DisplayName; var projectId = _projectResourceModel.GetProjectForResource(resource.Id).Value; _coscineLogger.AnalyticsLog( new AnalyticsLogObject { Type = "Action", Operation = "Owner Resource Quota Change", RoleId = _projectRoleModel.GetGetUserRoleForProject(projectId, user.Id).ToString(), ProjectId = projectId.ToString(), QuotaSize = new List<string> { $"{resourceTypeDisplayName}: {reserved}/{maximum}" }, ResourceId = resource.Id.ToString(), ApplicationsProfile = resource.ApplicationProfile }); } } }