api.py 5.93 KB
Newer Older
Michal Simon's avatar
Michal Simon committed
1
#   Copyright notice:
2
#   Copyright  Members of the EMI Collaboration, 2013.
3
#
Michal Simon's avatar
Michal Simon committed
4
#   See www.eu-emi.eu for details on the copyright holders
5
#
Michal Simon's avatar
Michal Simon committed
6
7
8
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
9
#
Michal Simon's avatar
Michal Simon committed
10
#       http://www.apache.org/licenses/LICENSE-2.0
11
#
Michal Simon's avatar
Michal Simon committed
12
13
14
15
16
17
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

18
import glob
19
20
21
import pylons

from routes import request_config
22
23
from webob.exc import HTTPNotFound

24
from fts3.model import SchemaVersion
25

26
from fts3rest.lib.api import doc
27
from fts3rest.lib.base import BaseController, Session
28
29
from fts3rest.lib.helpers import jsonify
from fts3rest.lib import api
30

Andrea Manzi's avatar
Andrea Manzi committed
31
API_VERSION = dict(major=3, minor=8, patch=2)
32
33


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def _get_fts_core_version():
    versions = []
    for match in glob.glob('/usr/share/doc/fts-libs-*'):
        try:
            major, minor, patch = match.split('-')[-1].split('.')
            versions.append(dict(major=major, minor=minor, patch=patch))
        except:
            pass
    if len(versions) == 0:
        return None
    elif len(versions) == 1:
        return versions[0]
    else:
        return versions

49
class ApiController(BaseController):
50
51
52
    """
    API documentation
    """
53

54
    def __init__(self):
55
        self.resources, self.apis, self.models = api.introspect()
56
        self.resources.sort(key=lambda res: res['id'])
57
        for r in self.apis.values():
58
            r.sort(key=lambda a: a['path'])
59
60
61
        # Add path to each resource
        for r in self.resources:
            r['path'] = '/' + r['id']
62

63
64
        self.fts_core_version = _get_fts_core_version()

65
66
    @jsonify
    def api_version(self):
67
68
69
        schema_v = Session.query(SchemaVersion)\
            .order_by(SchemaVersion.major.desc(), SchemaVersion.minor.desc(), SchemaVersion.patch.desc())\
            .first()
70
        return {
71
            'delegation': dict(major=1, minor=0, patch=0),
72
            'api': API_VERSION,
73
            'core': self.fts_core_version,
74
            'schema': schema_v,
75
            '_links': {
76
                'curies': [{'name': 'fts', 'href': 'https://gitlab.cern.ch/fts/fts3'}],
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

                'fts:whoami': {'href': '/whoami', 'title': 'Check user certificate'},

                'fts:joblist': {
                    'href': '/jobs{?vo_name,user_dn,dlg_id,state_in}',
                    'title': 'List of active jobs',
                    'templated': True
                },
                'fts:job': {
                    'href': '/jobs/{id}',
                    'title': 'Job information',
                    'templated': True,
                    'hints': {
                        'allow': ['GET', 'DELETE']
                    }
                },


                'fts:configaudit': {'href': '/config/audit', 'title': 'Configuration'},

                'fts:submitschema': {'href': '/api-docs/schema/submit', 'title': 'JSON schema of messages'},
                'fts:apidocs': {'href': '/api-docs/', 'title': 'API Documentation'},
                'fts:jobsubmit': {
                    'href': '/jobs',
                    'hints': {
                        'allow': ['POST'],
                        'representations': ['fts:submitschema']
                    }
                },

                'fts:optimizer': {'href': '/optimizer/', 'title': 'Optimizer'},

                'fts:archive':  {'href': '/archive/', 'title': 'Archive'}
            }
        }

ayllon's avatar
ayllon committed
113
    @jsonify
114
    def submit_schema(self):
115
        """
116
        Json-schema for the submission operation
117

118
        This can be used to validate the submission. For instance, in Python,
119
        jsonschema.validate
120
        """
121
        return api.SubmitSchema
122
123
124

    @jsonify
    def api_docs(self):
125
126
        """
        Auto-generated API documentation
127

128
129
        Compatible with Swagger-UI
        """
130
131
132
133
134
135
136
137
138
139
140
141
        return {
            'swaggerVersion': '1.2',
            'apis': self.resources,
            'info': {
                'title': 'FTS3 RESTful API',
                'description': 'FTS3 RESTful API documentation',
                'contact': 'fts-devel@cern.ch',
                'license': 'Apache 2.0',
                'licenseUrl': 'http://www.apache.org/licenses/LICENSE-2.0.html'
            }
        }

142
    @doc.response(404, 'The resource can not be found')
143
144
    @jsonify
    def resource_doc(self, resource):
145
146
147
        """
        Auto-generated API documentation for a specific resource
        """
148
        if resource not in self.apis:
149
            raise HTTPNotFound('API not found: ' + resource)
150
        return {
151
152
153
154
155
            'basePath': '/',
            'swaggerVersion': '1.2',
            'produces': ['application/json'],
            'resourcePath': '/' + resource,
            'authorizations': {},
156
157
            'apis': self.apis.get(resource, []),
            'models': self.models.get(resource, []),
158
        }
159
160
161
162
163

    def options_handler(self, path, environ):
        """
        Generates a response for an OPTIONS request
        """
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
        mapper = request_config(original=False).mapper
        mapper.create_regs()

        full_path = '/' + path
        routes = list()
        for route in mapper.matchlist:
            match = route.match(full_path)
            if isinstance(match, dict) or match:
                routes.append(route)

        if len(routes) == 0:
            raise HTTPNotFound()

        allowed = set()
        for route in routes:
            if route.conditions and 'method' in route.conditions:
                allowed.update(set(route.conditions['method']))

        # If only this handler matches, consider this a Not Found
        if allowed == set(['OPTIONS']):
184
185
186
187
            raise HTTPNotFound()

        pylons.response.headers['Allow'] = ', '.join(allowed)
        return None