Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
gerardparis committed Oct 20, 2016
2 parents c3b3e57 + 4dbd5c3 commit 68fb886
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 447 deletions.
9 changes: 5 additions & 4 deletions api/filters/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.conf.urls import url

import views

urlpatterns = [
Expand All @@ -9,13 +10,13 @@

# Deploy to tenant container or object
url(r'^/(?P<account>\w+)/deploy/(?P<filter_id>[0-9]+)/?$', views.filter_deploy),
url(r'^/(?P<account>\w+)/(?P<container>\w+)/deploy/(?P<filter_id>[0-9]+)/?$', views.filter_deploy),
url(r'^/(?P<account>\w+)/(?P<container>\w+)/(?P<swift_object>\w+)/deploy/(?P<filter_id>[0-9]+)/?$', views.filter_deploy),
url(r'^/(?P<account>\w+)/(?P<container>[-\w]+)/deploy/(?P<filter_id>[0-9]+)/?$', views.filter_deploy),
url(r'^/(?P<account>\w+)/(?P<container>[-\w]+)/(?P<swift_object>[-\w]+)/deploy/(?P<filter_id>[0-9]+)/?$', views.filter_deploy),

# Undeploy to tenant container or object
# url(r'^/(?P<account>\w+)/undeploy/(?P<filter_id>[0-9]+)/?$', views.filter_undeploy),
# url(r'^/(?P<account>\w+)/(?P<container>\w+)/undeploy/(?P<filter_id>[0-9]+)/?$', views.filter_undeploy),
# url(r'^/(?P<account>\w+)/(?P<container>\w+)/(?P<swift_object>\w+)/undeploy/(?P<filter_id>[0-9]+)/?$', views.filter_undeploy),
# url(r'^/(?P<account>\w+)/(?P<container>[-\w]+)/undeploy/(?P<filter_id>[0-9]+)/?$', views.filter_undeploy),
# url(r'^/(?P<account>\w+)/(?P<container>[-\w]+)/(?P<swift_object>[-\w]+)/undeploy/(?P<filter_id>[0-9]+)/?$', views.filter_undeploy),

url(r'^/dependencies/?$', views.dependency_list),
url(r'^/dependencies/(?P<dependency_id>\w+)/?$', views.dependency_detail),
Expand Down
5 changes: 4 additions & 1 deletion api/registry/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.conf.urls import url

import views

urlpatterns = [
Expand Down Expand Up @@ -31,6 +32,8 @@
url(r'^/object_type/(?P<object_type_name>\w+)/?$', views.object_type_detail),

url(r'^/nodes/?$', views.node_list),
url(r'^/nodes/(?P<node_id>[^/]+)/?$', views.node_detail)
url(r'^/nodes/(?P<node_id>[^/]+)/?$', views.node_detail),

url(r'^/nodes/(?P<node_id>[^/]+)/restart/?$', views.node_restart)

]
72 changes: 48 additions & 24 deletions api/registry/views.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import json
import mimetypes
import os
import re
from operator import itemgetter

from django.conf import settings
from django.core.servers.basehttp import FileWrapper
from django.http import HttpResponse
from django.http import StreamingHttpResponse
from django.views.decorators.csrf import csrf_exempt
from pyactive.controller import init_host, start_controller
from redis.exceptions import RedisError, DataError
from rest_framework import status
from rest_framework.exceptions import ParseError
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.views import APIView
from operator import itemgetter
from pyactive.controller import init_host, start_controller
from redis.exceptions import RedisError, DataError

from api.exceptions import SwiftClientError, StorletNotFoundException, FileSynchronizationException
import dsl_parser
from api.common_utils import is_valid_request, rsync_dir_with_nodes, to_json_bools, remove_extra_whitespaces, JSONResponse, get_redis_connection, get_project_list
from filters.views import set_filter, unset_filter
from api.exceptions import SwiftClientError, StorletNotFoundException, FileSynchronizationException
from filters.views import save_file, make_sure_path_exists
import dsl_parser

import json
import mimetypes
import os
import re
from filters.views import set_filter, unset_filter

host = None
remote_host = None
Expand Down Expand Up @@ -78,12 +78,12 @@ def load_policies():
for action_info in rule_parsed.action_list:
if action_info.transient:
print 'Transient rule:', policy_data['policy_description']
rules[policy] = host.spawn_id(str(policy), settings.RULE_TRANSIENT_CLASS, settings.RULE_TRANSIENT_MAIN,
rules[policy] = host.spawn_id(str(policy), settings.RULE_TRANSIENT_CLASS, settings.RULE_TRANSIENT_MAIN,
[rule_parsed, action_info, target, host])
rules[policy].start_rule()
else:
print 'Rule:', policy_data['policy_description']
rules[policy] = host.spawn_id(str(policy), settings.RULE_CLASS, settings.RULE_MAIN,
rules[policy] = host.spawn_id(str(policy), settings.RULE_CLASS, settings.RULE_MAIN,
[rule_parsed, action_info, target, host])
rules[policy].start_rule()

Expand Down Expand Up @@ -283,7 +283,7 @@ def start_metric(metric_id, actor_id):
print "- Metric, Starting workload metric actor " + str(metric_id)
try:
if metric_id not in metrics:
metrics[metric_id] = host.spawn_id(actor_id, settings.METRIC_CLASS, settings.METRIC_MAIN,
metrics[metric_id] = host.spawn_id(actor_id, settings.METRIC_CLASS, settings.METRIC_MAIN,
["amq.topic", actor_id, "metrics." + actor_id])
metrics[metric_id].init_consum()
except Exception as e:
Expand Down Expand Up @@ -351,7 +351,7 @@ def metric_module_detail(request, metric_module_id):

r.delete("workload_metric:" + str(metric_id))
keys = len(r.keys("workload_metric:*"))
r.set('workload_metrics:id',keys)
r.set('workload_metrics:id', keys)

return JSONResponse('Workload metric has been deleted', status=status.HTTP_204_NO_CONTENT)
except DataError:
Expand Down Expand Up @@ -814,7 +814,7 @@ def policy_list(request):
token = is_valid_request(request)
if not token:
return JSONResponse('You must be authenticated as admin.', status=status.HTTP_401_UNAUTHORIZED)

try:
r = get_redis_connection()
except RedisError:
Expand Down Expand Up @@ -867,7 +867,7 @@ def policy_list(request):

if condition_list:
# Dynamic Rule
#print('Rule parsed:', rule_parsed)
# print('Rule parsed:', rule_parsed)
deploy_policy(r, rule_string, rule_parsed)
else:
# Static Rule
Expand Down Expand Up @@ -959,7 +959,7 @@ def dynamic_policy_detail(request, policy_id):
r.delete('policy:' + policy_id)
policies_ids = r.keys('policy:*')
if len(policies_ids) == 0:
r.set('policies:id',0)
r.set('policies:id', 0)
return JSONResponse('Policy has been deleted', status=204)

return JSONResponse('Method ' + str(request.method) + ' not allowed.', status=405)
Expand Down Expand Up @@ -1028,33 +1028,57 @@ def deploy_policy(r, rule_string, parsed_rule):
rule_id = 'policy:' + str(policy_id)

if action_info.transient:
#print 'Transient rule:', parsed_rule
# print 'Transient rule:', parsed_rule
rules[policy_id] = host.spawn_id(rule_id, settings.RULE_TRANSIENT_CLASS, settings.RULE_TRANSIENT_MAIN,
[rules_to_parse[key], action_info, key, host])
[rules_to_parse[key], action_info, key, host])
location = os.path.join(settings.RULE_TRANSIENT_CLASS, settings.RULE_TRANSIENT_MAIN)
is_transient = True
else:
#print 'Rule:', parsed_rule
# print 'Rule:', parsed_rule
rules[policy_id] = host.spawn_id(rule_id, settings.RULE_CLASS, settings.RULE_MAIN,
[rules_to_parse[key], action_info, key, host])
[rules_to_parse[key], action_info, key, host])
location = os.path.join(settings.RULE_CLASS, settings.RULE_MAIN)
is_transient = False

rules[policy_id].start_rule()

# FIXME Should we recreate a static rule for each target and action??
condition_re = re.compile(r'.* (WHEN .*) DO .*', re.M|re.I)
condition_re = re.compile(r'.* (WHEN .*) DO .*', re.M | re.I)
condition_str = condition_re.match(rule_string).group(1)

tmp_rule_string = rule_string.replace(condition_str, '').replace('TRANSIENT', '')
static_policy_rule_string = remove_extra_whitespaces(tmp_rule_string)

# Add policy into redis
policy_location = os.path.join(settings.PYACTIVE_URL, location, str(rule_id))
r.hmset('policy:' + str(policy_id), {"id": policy_id,
r.hmset('policy:' + str(policy_id), {"id": policy_id,
"policy": static_policy_rule_string,
"policy_description": rule_string,
"condition": condition_str.replace('WHEN ', ''),
"transient": is_transient,
"policy_location": policy_location,
"alive": True})


@csrf_exempt
def node_restart(request, node_id):
# Validate request: only a user with admin role can access to this method
token = is_valid_request(request)
if not token:
return JSONResponse('You must be authenticated as admin.', status=status.HTTP_401_UNAUTHORIZED)

try:
r = get_redis_connection()
except RedisError:
return JSONResponse('Error connecting with DB', status=status.HTTP_500_INTERNAL_SERVER_ERROR)

if request.method == 'PUT':
node = r.hgetall('node:' + str(node_id))
data = {'node_ip': node['ip'], 'ssh_username': node['ssh_username'], 'ssh_password': node['ssh_password']}
restart_command = 'sshpass -p {ssh_password} ssh {ssh_username}@{node_ip} sudo swift-init all restart'.format(**data)
# print "System: %s" % rsync_command
ret = os.system(restart_command)
if ret != 0:
raise FileSynchronizationException("An error occurred restarting Swift nodes")

return JSONResponse('Method ' + str(request.method) + ' not allowed.', status=status.HTTP_405_METHOD_NOT_ALLOWED)
2 changes: 1 addition & 1 deletion api/swift/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
url(r'^/tenants/?$', views.tenants_list),
url(r'^/storage_policies/?$', views.storage_policy_list),
url(r'^/spolicies/?$', views.storage_policies),
url(r'^/locality/(?P<account>\w+)(?:/(?P<container>\w+))(?:/(?P<swift_object>\w+))?/$', views.locality_list),
url(r'^/locality/(?P<account>\w+)(?:/(?P<container>[-\w]+))(?:/(?P<swift_object>[-\w]+))?/$', views.locality_list),
url(r'^/sort_nodes/?$', views.sort_list),
url(r'^/sort_nodes/(?P<id>[0-9]+)/?$', views.sort_detail),
]
42 changes: 42 additions & 0 deletions doc/api_specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Crystal Controller API Specification
====================================
**Table of Contents**

- [Common aspects](#common-aspects)
- [Error handling](#error-handling)
- [Authentication](#authentication)
- [API specification](#api-specification)

## Common aspects

### Error handling

Errors are returned using standard HTTP error code syntax. Any additional info is included in the body of the return call, JSON-formatted. Error codes not listed here are in the REST API methods listed below.

Standard API errors

CODE | DESCRIPTION
--- | ---
**400** | Bad input parameter. Error message should indicate which one and why.
**401** | Authorization required. The presented credentials, if any, were not sufficient to access the folder resource. Returned if an application attempts to use an access token after it has expired.
**403** | Forbidden. The requester does not have permission to access the specified resource.
**404** | File or folder not found at the specified path.
**405** | Request method not expected (generally should be GET or POST).
**5xx** | Server error

### Authentication

After successfully receiving the credentials from keystone, it is necessary that all API calls will be authenticated. To authenticate the calls, it needs to add the header explained in the next table:

OAuth PARAMETER | DESCRIPTION
--- | ---
**X-Auth-Token** | Admin token obtained after a successful authentication in keystone.

## API specification

The Crystal Controller API structures the allowed operations in different groups:

- [BW operations](/doc/api_specification_bw.md)
- [Filters operations](/doc/api_specification_filters.md)
- [Registry operations](/doc/api_specification_registry.md)
- [Swift operations](/doc/api_specification_swift.md)

0 comments on commit 68fb886

Please sign in to comment.