Commit 4215972a authored by Marcel Nellesen's avatar Marcel Nellesen
Browse files

Merge branch 'Topic/1279-quotaManagement' into 'Product/588-quotaManagement'

Topic/1279 quota management

See merge request !121
parents 1799b289 03172002
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);
......
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; }
}
}
......@@ -69,6 +69,9 @@
<Reference Include="Coscine.Database.T4, Version=1.27.0.0, Culture=neutral, PublicKeyToken=84b4c404a0696261, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.Database.1.27.0\lib\net461\Coscine.Database.T4.dll</HintPath>
</Reference>
<Reference Include="Coscine.ECSManager, Version=1.1.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.ECSManager.1.1.1-topic-1279-quota0001\lib\net461\Coscine.ECSManager.dll</HintPath>
</Reference>
<Reference Include="Coscine.JwtHandler, Version=1.2.0.0, Culture=neutral, PublicKeyToken=aaacf41df3a6253c, processorArchitecture=MSIL">
<HintPath>..\packages\Coscine.JwtHandler.1.2.0\lib\net461\Coscine.JwtHandler.dll</HintPath>
</Reference>
......@@ -96,6 +99,9 @@
<Reference Include="dotNetRDF.Data.Virtuoso, Version=2.6.0.0, Culture=neutral, PublicKeyToken=6055ffe4c97cc780, processorArchitecture=MSIL">
<HintPath>..\packages\dotNetRDF.Data.Virtuoso.2.6.0\lib\net40\dotNetRDF.Data.Virtuoso.dll</HintPath>
</Reference>
<Reference Include="ECSManagementSDK, Version=1.2.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ECSManagementSDK.1.2.2\lib\net45\ECSManagementSDK.dll</HintPath>
</Reference>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.4.4\lib\net45\EntityFramework.dll</HintPath>
</Reference>
......@@ -543,6 +549,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 +690,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>
......
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; }
}
}
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; }
}
}
......@@ -8,6 +8,7 @@
<package id="Coscine.ApiCommons" version="1.11.0" targetFramework="net472" />
<package id="Coscine.Configuration" version="1.5.0" targetFramework="net472" />
<package id="Coscine.Database" version="1.27.0" targetFramework="net472" />
<package id="Coscine.ECSManager" version="1.1.1-topic-1279-quota0001" targetFramework="net472" />
<package id="Coscine.JwtHandler" version="1.2.0" targetFramework="net472" />
<package id="Coscine.Logging" version="1.2.0" targetFramework="net472" />
<package id="Coscine.Metadata" version="1.5.0" targetFramework="net472" />
......@@ -17,12 +18,14 @@
<package id="Coscine.ResourceTypeBase" version="1.4.0" targetFramework="net472" />
<package id="dotNetRDF" version="2.6.0" targetFramework="net472" />
<package id="dotNetRDF.Data.Virtuoso" version="2.6.0" targetFramework="net472" />
<package id="ECSManagementSDK" version="1.2.2" targetFramework="net472" />
<package id="EntityFramework" version="6.4.4" targetFramework="net472" />
<package id="HtmlAgilityPack" version="1.11.24" targetFramework="net472" />
<package id="linq2db" version="3.1.1" targetFramework="net472" />
<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" />
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment