diff --git a/src/Project/Controllers/ProjectController.cs b/src/Project/Controllers/ProjectController.cs index 61a2b8cca3e0fca8c86d7e9b6829a67c54b7c38c..06bebe4bea0839f4da3d295e76d977037d0ff4ea 100644 --- a/src/Project/Controllers/ProjectController.cs +++ b/src/Project/Controllers/ProjectController.cs @@ -1,5 +1,7 @@ using Coscine.Action; using Coscine.Action.EventArgs; +using Coscine.Api.Project.ParameterObjects; +using Coscine.Api.Project.ReturnObjects; using Coscine.ApiCommons; using Coscine.ApiCommons.Factories; using Coscine.Configuration; @@ -32,8 +34,12 @@ namespace Coscine.Api.Project.Controllers private readonly Emitter _emitter; private readonly ActivatedFeaturesModel _activatedFeaturesModel; private readonly ProjectRoleModel _projectRoleModel; + private readonly ProjectQuotaModel _projectQuotaModel; + private readonly ResourceTypeModel _resourceTypeModel; + private readonly ResourceModel _resourceModel; private readonly CoscineLogger _coscineLogger; private readonly AnalyticsLogObject _analyticsLogObject; + private readonly int _maxAvailable = 100; /// <summary> /// ProjectController constructor @@ -47,6 +53,9 @@ namespace Coscine.Api.Project.Controllers _emitter = new Emitter(_configuration); _activatedFeaturesModel = new ActivatedFeaturesModel(); _projectRoleModel = new ProjectRoleModel(); + _resourceTypeModel = new ResourceTypeModel(); + _resourceModel = new ResourceModel(); + _projectQuotaModel = new ProjectQuotaModel(); _coscineLogger = new CoscineLogger(logger); _analyticsLogObject = new AnalyticsLogObject(); } @@ -155,56 +164,231 @@ namespace Coscine.Api.Project.Controllers } /// <summary> - /// Retrieves the quota for the selected project + /// Retrieves the quota for the selected project. /// </summary> - /// <param name="id">Id of the resource</param> - /// <returns>Json object or Statuscode 401</returns> - [HttpGet("[controller]/{id}/quotas")] - public ActionResult<IEnumerable<ProjectQuota>> Quotas(string id) + /// <param name="projectId">Id of the project.</param> + /// <returns>List of project quotas</returns> + [HttpGet("[controller]/{projectId}/quota/-/all")] + public ActionResult<IEnumerable<ProjectQuota>> Quotas(string projectId) { var user = _authenticator.GetUser(); - var projectObject = ObjectFactory<ProjectObject>.DeserializeFromStream(Request.Body); - var guidId = Guid.Parse(id); - var project = _projectModel.GetById(guidId); - if (_projectModel.HasAccess(user, project, UserRoles.Owner)) + + if (!Guid.TryParse(projectId, out Guid projectGuid)) + { + return BadRequest($"{projectId} is not a guid."); + } + + var project = _projectModel.GetById(projectGuid); + + if (project == null) { - ProjectQuotaModel projectQuotaModel = new ProjectQuotaModel(); - var projectQuotas = - projectQuotaModel.GetAllWhere((projectQuota) => - projectQuota.ProjectId == guidId - && projectQuota.ResourceType.Enabled == true) - .Select((projectQuota) => projectQuotaModel.CreateReturnObjectFromDatabaseObject(projectQuota)); + return NotFound($"Could not find project with id: {projectId}"); + } + + if (!_projectModel.HasAccess(user, project, UserRoles.Owner)) + { + return Unauthorized("The user is not authorized to perform a get on the selected project!"); + } + var projectQuotas = + _projectQuotaModel.GetAllWhere((projectQuota) => + projectQuota.ProjectId == projectGuid + && projectQuota.ResourceType.Enabled == true); - ResourceModel resourceModel = new ResourceModel(); - RDSResourceTypeModel rdsResourceTypeModel = new RDSResourceTypeModel(); - var returnList = new List<dynamic>(); - foreach (var projectQuota in projectQuotas) + var resourceTypes = _resourceTypeModel.GetAllWhere(x => x.Enabled); + + return Json(resourceTypes.Select(x => + { + var projectQuota = _projectQuotaModel.GetWhere((y) => + y.ProjectId == projectGuid && + y.ResourceTypeId == x.Id); + return new ProjectQuotaReturnObject { - // TODO: Cleanup quota and give it to every resource, this hard coded solution seems not scalable - if (projectQuota.ResourceType.DisplayName == "rds") - { - var resources = resourceModel.GetAllWhere((resource) => - resource.TypeId == projectQuota.ResourceType.Id - && (from connection in resource.ProjectResourceResourceIdIds - where connection.ProjectId == guidId - select connection).Any()); - - var size = resources.Sum((resource) => - rdsResourceTypeModel.GetById(resource.ResourceTypeOptionId.Value).Size); - returnList.Add(new { - type = projectQuota.ResourceType.DisplayName, - available = projectQuota.Quotas, - allocated = size - }); - } - } - return Json(returnList); + Id = x.Id, + Name = x.DisplayName, + Used = CalculateUsed(x, projectGuid), + Allocated = projectQuota == null ? 0 : projectQuota.Quota + }; + })); + } + + private int CalculateUsed(ResourceType resourceType, Guid projectId) + { + var resourceTypeDefinition = ResourceTypeFactory.CreateResourceTypeObject(resourceType.DisplayName, _configuration); + + var resources = _resourceModel.GetAllWhere((resource) => + (from projectResource in resource.ProjectResourceResourceIdIds + where projectResource.ProjectId == projectId + select projectResource).Any() && + resource.TypeId == resourceType.Id); + + var used = resources.Sum(y => resourceTypeDefinition.GetResourceQuotaAvailable(y.Id.ToString(), _resourceModel.GetResourceTypeOptions(y.Id)).Result); + return (int)used; + } + + /// <summary> + /// Retrieves the quota for the selected project and resource Type. + /// </summary> + /// <param name="projectId">Id of the project</param> + /// <param name="resourceTypeId">Id of the resource type</param> + /// <returns>The project quota for the resource type.</returns> + [HttpGet("[controller]/{projectId}/quota/{resourceTypeId}")] + public ActionResult<ProjectQuotaReturnObject> Quota(string projectId, string resourceTypeId) + { + var user = _authenticator.GetUser(); + + if (!Guid.TryParse(projectId, out Guid projectGuid)) + { + return BadRequest($"{projectId} is not a guid."); } - else + + var project = _projectModel.GetById(projectGuid); + + if (project == null) + { + return NotFound($"Could not find project with id: {projectId}"); + } + + if (!_projectModel.HasAccess(user, project, UserRoles.Owner)) + { + return Unauthorized("The user is not authorized to perform a get on the selected project!"); + } + + if (!Guid.TryParse(resourceTypeId, out Guid resourceTypeGuid)) + { + return BadRequest($"{resourceTypeId} is not a guid."); + } + + var resourceType = _resourceTypeModel.GetById(resourceTypeGuid); + + if (resourceType == null || !resourceType.Enabled) + { + return NotFound($"Could not find resourceType with id: {resourceTypeId}"); + } + + var projectQuota = + _projectQuotaModel.GetWhere((x) => + x.ProjectId == projectGuid && + x.ResourceTypeId == resourceTypeGuid); + + var projectQuotaReturnObject = new ProjectQuotaReturnObject + { + Id = resourceTypeGuid, + Name = resourceType.DisplayName, + Used = CalculateUsed(resourceType, projectGuid), + Allocated = projectQuota.Quota}; + + return Json(projectQuotaReturnObject); + } + + /// <summary> + /// Get the max quota for a resource type. + /// </summary> + /// <param name="projectId">Id of the project.</param> + /// <param name="resourceTypeId">Id of the resource</param> + /// <returns>The maximum value for the quota.</returns> + [HttpGet("[controller]/{projectId}/quota/{resourceTypeId}/max")] + public ActionResult<MaxProjectQuota> GetQuotaMax(string projectId, string resourceTypeId) + { + var user = _authenticator.GetUser(); + + if (!Guid.TryParse(projectId, out Guid projectGuid)) + { + return BadRequest($"{projectId} is not a guid."); + } + + var project = _projectModel.GetById(projectGuid); + + if (project == null) + { + return NotFound($"Could not find project with id: {projectId}"); + } + + if (!_projectModel.HasAccess(user, project, UserRoles.Owner)) + { + return Unauthorized("The user is not authorized to perform a get on the selected project!"); + } + + if (!Guid.TryParse(resourceTypeId, out Guid resourceTypeGuid)) + { + return BadRequest($"{resourceTypeId} is not a guid."); + } + + var resourceType = _resourceTypeModel.GetById(resourceTypeGuid); + + if (resourceType == null || !resourceType.Enabled) + { + return NotFound($"Could not find resourceType with id: {resourceTypeId}"); + } + + return Json(new MaxProjectQuota { Id = resourceTypeGuid, Available = _maxAvailable }); + } + + /// <summary> + /// Update the project quota. + /// </summary> + /// <param name="projectId">Id of the project.</param> + /// <param name="resourceTypeId">Id of the resource.</param> + /// <param name="updateProjectQuotaObject">Object containing the update values.</param> + /// <returns>NoContent (204).</returns> + [HttpPost("[controller]/{projectId}/quota/{resourceTypeId}")] + public IActionResult UpdateQuota(string projectId, string resourceTypeId, [FromBody]UpdateProjectQuotaObject updateProjectQuotaObject) + { + var user = _authenticator.GetUser(); + + if (!Guid.TryParse(projectId, out Guid projectGuid)) + { + return BadRequest($"{projectId} is not a guid."); + } + + var project = _projectModel.GetById(projectGuid); + + if (project == null) + { + return NotFound($"Could not find project with id: {projectId}"); + } + + if (!_projectModel.HasAccess(user, project, UserRoles.Owner)) { return Unauthorized("The user is not authorized to perform a get on the selected project!"); } + + if (!Guid.TryParse(resourceTypeId, out Guid resourceTypeGuid)) + { + return BadRequest($"{resourceTypeId} is not a guid."); + } + + var resourceType = _resourceTypeModel.GetById(resourceTypeGuid); + + if (resourceType == null || !resourceType.Enabled) + { + return NotFound($"Could not find resourceType with id: {resourceTypeId}"); + } + + if (updateProjectQuotaObject.Allocated < 0) + { + return BadRequest($"Allocated {updateProjectQuotaObject.Allocated}. Cannot be less than 0."); + } + + var projectQuotaForCurrent = _projectQuotaModel.GetWhere(x => x.ProjectId == projectGuid && x.ResourceTypeId == resourceTypeGuid); + var used = CalculateUsed(resourceType, projectGuid); + + if(used > updateProjectQuotaObject.Allocated) + { + return BadRequest($"Cannot set quota ({updateProjectQuotaObject.Allocated}) below the used value ({used})."); + } + + var projectQuotaForAll = _projectQuotaModel.GetAllWhere(x => x.ProjectId == projectGuid).Sum(x => x.Quota); + + if(projectQuotaForAll - projectQuotaForCurrent.Quota + updateProjectQuotaObject.Allocated > _maxAvailable) + { + return BadRequest($"Cannot set quota to {updateProjectQuotaObject.Allocated}. In combination with the other quotas ({projectQuotaForAll - projectQuotaForCurrent.Quota}), it would exceed the limit of {_maxAvailable}"); + } + + projectQuotaForCurrent.Quota = updateProjectQuotaObject.Allocated; + _projectQuotaModel.Update(projectQuotaForCurrent); + + return NoContent(); } /// <summary> @@ -319,10 +503,9 @@ namespace Coscine.Api.Project.Controllers projectInstituteModel.Delete(projectInstitute); } - var projectQuotaModel = new ProjectQuotaModel(); - foreach (var projectQuota in projectQuotaModel.GetAllWhere((Quota) => Quota.ProjectId == project.Id)) + foreach (var projectQuota in _projectQuotaModel.GetAllWhere((Quota) => Quota.ProjectId == project.Id)) { - projectQuotaModel.Delete(projectQuota); + _projectQuotaModel.Delete(projectQuota); } _activatedFeaturesModel.DeactivateAllFeatures(project); diff --git a/src/Project/ParameterObjects/UpdateProjectQuotaObject.cs b/src/Project/ParameterObjects/UpdateProjectQuotaObject.cs new file mode 100644 index 0000000000000000000000000000000000000000..23a1fa8fe7060d2c626de62f223264444df8c1d0 --- /dev/null +++ b/src/Project/ParameterObjects/UpdateProjectQuotaObject.cs @@ -0,0 +1,19 @@ +using System; + +namespace Coscine.Api.Project.ParameterObjects +{ + /// <summary> + /// Parameter object containing the update informations. + /// </summary> + public class UpdateProjectQuotaObject + { + /// <summary> + /// Id of the resourceType + /// </summary> + public Guid Id { get; set; } + /// <summary> + /// New Quota value. + /// </summary> + public int Allocated { get; set; } + } +} diff --git a/src/Project/Project.csproj b/src/Project/Project.csproj index 317f1f6b5b78fe9880f731ab8331379a58387987..908e79a00e06170329afbcb0ca324d214fd8e656 100644 --- a/src/Project/Project.csproj +++ b/src/Project/Project.csproj @@ -543,6 +543,9 @@ <Private>True</Private> </Reference> <Reference Include="System.Net" /> + <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath> + </Reference> <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Numerics" /> <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> @@ -681,8 +684,11 @@ <Compile Include="Controllers\RoleController.cs" /> <Compile Include="Controllers\SubProjectController.cs" /> <Compile Include="Controllers\ProjectRoleController.cs" /> + <Compile Include="ParameterObjects\UpdateProjectQuotaObject.cs" /> <Compile Include="Program.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="ReturnObjects\MaxProjectQuota.cs" /> + <Compile Include="ReturnObjects\ProjectQuotaReturnObject.cs" /> <Compile Include="Startup.cs" /> </ItemGroup> <ItemGroup> diff --git a/src/Project/ReturnObjects/MaxProjectQuota.cs b/src/Project/ReturnObjects/MaxProjectQuota.cs new file mode 100644 index 0000000000000000000000000000000000000000..785206993d13df571b453da8a23465e791092a80 --- /dev/null +++ b/src/Project/ReturnObjects/MaxProjectQuota.cs @@ -0,0 +1,19 @@ +using System; + +namespace Coscine.Api.Project.ReturnObjects +{ + /// <summary> + /// Return object containing the maximum project quota. + /// </summary> + public class MaxProjectQuota + { + /// <summary> + /// The resource type id. + /// </summary> + public Guid Id { get; set; } + /// <summary> + /// Available amount in gb. + /// </summary> + public int Available { get; set; } + } +} diff --git a/src/Project/ReturnObjects/ProjectQuotaReturnObject.cs b/src/Project/ReturnObjects/ProjectQuotaReturnObject.cs new file mode 100644 index 0000000000000000000000000000000000000000..e2a218f557e5e77aa23505f410b98aace5260f6e --- /dev/null +++ b/src/Project/ReturnObjects/ProjectQuotaReturnObject.cs @@ -0,0 +1,27 @@ +using System; + +namespace Coscine.Api.Project.ReturnObjects +{ + /// <summary> + /// Contains information about the quota of a project by resource type. + /// </summary> + public class ProjectQuotaReturnObject + { + /// <summary> + /// Id of the resoure type. + /// </summary> + public Guid Id { get; set; } + /// <summary> + /// Display name of the resource type. + /// </summary> + public string Name { get; set; } + /// <summary> + /// How much space is used by the resources (in gb). + /// </summary> + public int Used { get; set; } + /// <summary> + /// How much space is availabe to be taken by resources (in gb). + /// </summary> + public int Allocated { get; set; } + } +} diff --git a/src/Project/packages.config b/src/Project/packages.config index e2dd8531a8aacd5f127304cdcd8ada5b1dfc816d..701ac45d1957dd8c38f3d9ee4987615db7be9973 100644 --- a/src/Project/packages.config +++ b/src/Project/packages.config @@ -23,6 +23,7 @@ <package id="linq2db.SqlServer" version="2.6.4" targetFramework="net472" /> <package id="linq2db.t4models" version="2.6.4" targetFramework="net472" /> <package id="LinqKit" version="1.1.17" targetFramework="net472" /> + <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net472" /> <package id="Microsoft.AspNetCore" version="2.2.0" targetFramework="net472" /> <package id="Microsoft.AspNetCore.Antiforgery" version="2.2.0" targetFramework="net472" /> <package id="Microsoft.AspNetCore.Authentication" version="2.2.0" targetFramework="net472" />