Commit 29ec9126 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis

astakos: Describe and implement API for projects

parent 2351c674
......@@ -28,6 +28,8 @@ Astakos
* Improve recording of project, application, and membership actions.
* Implement API calls for projects.
Cyclades
--------
......
......@@ -46,6 +46,13 @@ Resource and Quota Service API (Astakos)
Resource and Quota API <quota-api-guide.rst>
Project API
===========
.. toctree::
:maxdepth: 2
Project API <project-api-guide.rst>
Compute Service API (Cyclades)
==============================
......
Projects
--------
Astakos allows users to create *projects*. Through a project, one can ask for
additional resources on the virtual infrastructure for a certain amount of
time. All users admitted to the project gain access to these resources.
Retrieve List of Projects
.........................
**GET** /account/v1.0/projects
Returns all accessible projects. See below.
==================== =========================
Request Header Name Value
==================== =========================
X-Auth-Token User authentication token
==================== =========================
Request can specify a filter.
**Example Request**:
.. code-block:: javascript
{
"filter": {
"state": ["active", "suspended"],
"owner": [uuid]
}
}
**Response Codes**:
====== =====================
Status Description
====== =====================
200 Success
400 Bad Request
401 Unauthorized (Missing token)
500 Internal Server Error
====== =====================
**Example Successful Response**:
List of project details. See below.
Retrieve a Project
..................
**GET** /account/v1.0/projects/<proj_id>
==================== =========================
Request Header Name Value
==================== =========================
X-Auth-Token User authentication token
==================== =========================
A project is accessible when the request user is admin, project owner,
applicant or member, or the project is active.
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
401 Unauthorized (Missing token)
403 Forbidden
404 Not Found
500 Internal Server Error
====== ============================
**Example Successful Response**:
.. code-block:: javascript
{
"id": proj_id,
"application": app_id,
"state": "pending" | "active" | "denied" | "dismissed" | "cancelled" | "suspended" | "terminated",
"creation_date": "2013-06-26T11:48:06.579100+00:00",
"name": "name",
"owner": uuid,
"homepage": homepage or null,
"description": description or null,
"start_date": date,
"end_date": date,
"join_policy": "auto" | "moderated" | "closed",
"leave_policy": "auto" | "moderated" | "closed",
"max_members": int or null
"resources": {"cyclades.vm": {"project_capacity": int or null,
"member_capacity": int
}
}
# only if request user is admin or project owner:
"comments": comments,
"pending_application": last pending app id or null,
"deactivation_date": date # if applicable
}
Create a Project
................
**POST** /account/v1.0/projects
==================== =========================
Request Header Name Value
==================== =========================
X-Auth-Token User authentication token
==================== =========================
**Example Request**:
.. code-block:: javascript
{
"name": name,
"owner": uuid, # if omitted, request user assumed
"homepage": homepage, # optional
"description": description, # optional
"comments": comments, # optional
"start_date": date, # optional
"end_date": date,
"join_policy": "auto" | "moderated" | "closed", # default: "moderated"
"leave_policy": "auto" | "moderated" | "closed", # default: "auto"
"resources": {"cyclades.vm": {"project_capacity": int or null,
"member_capacity": int
}
}
}
**Response Codes**:
====== ============================
Status Description
====== ============================
201 Created
400 Bad Request
401 Unauthorized (Missing token)
403 Forbidden
409 Conflict
500 Internal Server Error
====== ============================
**Example Successful Response**:
.. code-block:: javascript
{
"id": project_id,
"application": application_id
}
Modify a Project
................
**POST** /account/v1.0/projects/<proj_id>
==================== =========================
Request Header Name Value
==================== =========================
X-Auth-Token User authentication token
==================== =========================
**Example Request**:
As above.
**Response Codes**:
====== ============================
Status Description
====== ============================
201 Created
400 Bad Request
401 Unauthorized (Missing token)
403 Forbidden
404 Not Found
409 Conflict
500 Internal Server Error
====== ============================
**Example Successful Response**:
As above.
Take Action on a Project
........................
**POST** /account/v1.0/projects/<proj_id>/action
==================== =========================
Request Header Name Value
==================== =========================
X-Auth-Token User authentication token
==================== =========================
**Example Request**:
.. code-block:: javascript
{
<action>: "reason"
}
<action> can be: "suspend", "unsuspend", "terminate", "reinstate"
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
400 Bad Request
401 Unauthorized (Missing token)
403 Forbidden
404 Not Found
409 Conflict
500 Internal Server Error
====== ============================
Retrieve List of Applications
.............................
**GET** /account/v1.0/projects/apps
==================== =========================
Request Header Name Value
==================== =========================
X-Auth-Token User authentication token
==================== =========================
Get all accessible applications. See below.
**Example optional request**
.. code-block:: javascript
{
"project": <project_id>
}
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
400 Bad Request
401 Unauthorized (Missing token)
500 Internal Server Error
====== ============================
**Example Successful Response**:
List of application details. See below.
Retrieve an Application
.......................
**GET** /account/v1.0/projects/apps/<app_id>
==================== =========================
Request Header Name Value
==================== =========================
X-Auth-Token User authentication token
==================== =========================
An application is accessible when the request user is admin or the
application owner/applicant.
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
401 Unauthorized (Missing token)
403 Forbidden
404 Not Found
500 Internal Server Error
====== ============================
**Example Successful Response**
.. code-block:: javascript
{
"id": app_id,
"project": project_id,
"state": "pending" | "approved" | "replaced" | "denied" | "dismissed" | "cancelled",
"name": "name",
"owner": uuid,
"applicant": uuid,
"homepage": homepage or null,
"description": description or null,
"start_date": date,
"end_date": date,
"join_policy": "auto" | "moderated" | "closed",
"leave_policy": "auto" | "moderated" | "closed",
"max_members": int or null
"comments": comments,
"resources": {"cyclades.vm": {"project_capacity": int or null,
"member_capacity": int
}
}
}
Take Action on an Application
.............................
**POST** /account/v1.0/projects/apps/<app_id>/action
==================== ============================
Request Header Name Value
==================== ============================
X-Auth-Token User authentication token
==================== ============================
**Example Request**:
.. code-block:: javascript
{
<action>: "reason"
}
<action> can be one of "approve", "deny", "dismiss", "cancel".
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
400 Bad Request
401 Unauthorized (Missing token)
403 Forbidden
404 Not Found
409 Conflict
500 Internal Server Error
====== ============================
Retrieve List of Memberships
............................
**GET** /account/v1.0/projects/memberships
==================== ============================
Request Header Name Value
==================== ============================
X-Auth-Token User authentication token
==================== ============================
Get all accessible memberships. See below.
**Example Optional Request**
.. code-block:: javascript
{
"project": <proj_id>
}
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
400 Bad Request
401 Unauthorized (Missing token)
500 Internal Server Error
====== ============================
**Example Successful Response**
List of memberships. See below.
Retrieve a Membership
.....................
**GET** /account/v1.0/projects/memberships/<memb_id>
==================== ============================
Request Header Name Value
==================== ============================
X-Auth-Token User authentication token
==================== ============================
A membership is accessible if the request user is admin, project owner or
the member.
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
401 Unauthorized (Missing token)
403 Forbidden
404 Not Found
500 Internal Server Error
====== ============================
**Example Successful Response**
.. code-block:: javascript
{
"id": id,
"user": uuid,
"project": project_id,
"state": "requested" | "accepted" | "leave_requested" | "suspended" | "rejected" | "cancelled" | "removed",
"requested": last_request_date,
"accepted": last_acceptance_date,
"removed": last_removal_date,
"allowed_actions": ["leave", "cancel", "accept", "reject", "remove"],
}
Take Action on a Membership
...........................
**POST** /account/v1.0/projects/memberships/<memb_id>/action
==================== ============================
Request Header Name Value
==================== ============================
X-Auth-Token User authentication token
==================== ============================
**Example Request**
.. code-block:: javascript
{
<action>: "reason"
}
<action> can be one of: "leave", "cancel", "accept", "reject", "remove"
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
400 Bad Request
401 Unauthorized (Missing token)
403 Forbidden
404 Not Found
409 Conflict
500 Internal Server Error
====== ============================
Create a Membership
...................
**POST** /account/v1.0/projects/memberships
==================== ============================
Request Header Name Value
==================== ============================
X-Auth-Token User authentication token
==================== ============================
**Example Requests**
.. code-block:: javascript
{
"join": {
"project": proj_id
}
}
.. code-block:: javascript
{
"enroll": {
"project": proj_id,
"user": "user@example.org"
}
}
**Response Codes**:
====== ============================
Status Description
====== ============================
200 Success
400 Bad Request
401 Unauthorized (Missing token)
403 Forbidden
409 Conflict
500 Internal Server Error
====== ============================
**Example Response**
.. code-block:: javascript
{
"id": membership_id
}
This diff is collapsed.
......@@ -58,6 +58,24 @@ astakos_account_v1_0 += patterns(
url(r'^service/user_catalogs/?$', 'get_uuid_displayname_catalogs'),
)
astakos_account_v1_0 += patterns(
'astakos.api.projects',
url(r'^projects/?$', 'projects', name='api_projects'),
url(r'^projects/(?P<project_id>\d+)/?$', 'project', name='api_project'),
url(r'^projects/(?P<project_id>\d+)/action/?$', 'project_action',
name='api_project_action'),
url(r'^projects/apps/?$', 'applications', name='api_applications'),
url(r'^projects/apps/(?P<app_id>\d+)/?$', 'application',
name='api_application'),
url(r'^projects/apps/(?P<app_id>\d+)/action/?$', 'application_action',
name='api_application_action'),
url(r'^projects/memberships/?$', 'memberships', name='api_memberships'),
url(r'^projects/memberships/(?P<memb_id>\d+)/?$', 'membership',
name='api_membership'),
url(r'^projects/memberships/(?P<memb_id>\d+)/action/?$',
'membership_action', name='api_membership_action'),
)
urlpatterns = patterns(
'',
url(r'^v1.0/', include(astakos_account_v1_0)),
......
......@@ -81,6 +81,18 @@ def xml_response(content, template, status_code=None):
return response
def read_json_body(request, default=None):
body = request.raw_post_data
if not body and request.method == "GET":
body = request.GET.get("body")
if not body:
return default
try:
return json.loads(body)
except json.JSONDecodeError:
raise faults.BadRequest("Request body should be in json format.")
def is_integer(x):
return isinstance(x, (int, long))
......@@ -227,3 +239,7 @@ def get_content_length(request):
if content_length is None:
raise faults.LengthRequired('Missing or invalid Content-Length header')
return content_length
def invert_dict(d):
return dict((v, k) for k, v in d.iteritems())
......@@ -280,7 +280,9 @@ def get_related_project_id(application_id):
def get_project_by_id(project_id):
try:
return Project.objects.get(id=project_id)
return Project.objects.select_related(
"application", "application__owner",
"application__applicant").get(id=project_id)
except Project.DoesNotExist:
m = _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id
raise ProjectNotFound(m)
......@@ -500,6 +502,14 @@ def remove_membership(memb_id, request_user=None, reason=None):
return membership
def enroll_member_by_email(project_id, email, request_user=None, reason=None):
try:
user = AstakosUser.objects.verified().get(email=email)
return enroll_member(project_id, user, request_user, reason=reason)
except AstakosUser.DoesNotExist:
raise ProjectConflict(astakos_messages.UNKNOWN_USERS)
def enroll_member(project_id, user, request_user=None, reason=None):
project = get_project_for_update(project_id)
try:
......
......@@ -1481,6 +1481,14 @@ class ProjectApplication(models.Model):
return presentation.PROJECT_MEMBER_LEAVE_POLICIES.get(policy)
class ProjectResourceGrantManager(models.Manager):
def grants_per_app(self, applications):
app_ids = [app.id for app in applications]
grants = self.filter(
project_application__in=app_ids).select_related("resource")
return _partition_by(lambda g: g.project_application_id, grants)
class ProjectResourceGrant(models.Model):
resource = models.ForeignKey(Resource)
......@@ -1489,6 +1497,8 @@ class ProjectResourceGrant(models.Model):
project_capacity = intDecimalField(null=True)
member_capacity = intDecimalField(default=0)
objects = ProjectResourceGrantManager()
class Meta:
unique_together = ("resource", "project_application")
......
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