feature 8029 change RO to python3. Using vim plugins
[osm/RO.git] / RO / osm_ro / wim / http_handler.py
1 # -*- coding: utf-8 -*-
2 ##
3 # Copyright 2018 University of Bristol - High Performance Networks Research
4 # Group
5 # All Rights Reserved.
6 #
7 # Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
8 # Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
9 #
10 # Licensed under the Apache License, Version 2.0 (the "License"); you may
11 # not use this file except in compliance with the License. You may obtain
12 # a copy of the License at
13 #
14 # http://www.apache.org/licenses/LICENSE-2.0
15 #
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19 # License for the specific language governing permissions and limitations
20 # under the License.
21 #
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact with: <highperformance-networks@bristol.ac.uk>
24 #
25 # Neither the name of the University of Bristol nor the names of its
26 # contributors may be used to endorse or promote products derived from
27 # this software without specific prior written permission.
28 #
29 # This work has been performed in the context of DCMS UK 5G Testbeds
30 # & Trials Programme and in the framework of the Metro-Haul project -
31 # funded by the European Commission under Grant number 761727 through the
32 # Horizon 2020 and 5G-PPP programmes.
33 ##
34
35 """This module works as an extension to the toplevel ``httpserver`` module,
36 implementing callbacks for the HTTP routes related to the WIM features of OSM.
37
38 Acting as a front-end, it is responsible for converting the HTTP request
39 payload into native python objects, calling the correct engine methods
40 and converting back the response objects into strings to be send in the HTTP
41 response payload.
42
43 Direct domain/persistence logic should be avoided in this file, instead
44 calls to other layers should be done.
45 """
46 import logging
47
48 from bottle import request
49
50 from .. import utils
51 from ..http_tools.errors import ErrorHandler
52 from ..http_tools.handler import BaseHandler, route
53 from ..http_tools.request_processing import (
54 filter_query_string,
55 format_in,
56 format_out
57 )
58 from .engine import WimEngine
59 from .persistence import WimPersistence
60 from .schemas import (
61 wim_account_schema,
62 wim_edit_schema,
63 wim_port_mapping_schema,
64 wim_schema
65 )
66
67
68 class WimHandler(BaseHandler):
69 """HTTP route implementations for WIM related URLs
70
71 Arguments:
72 db: instance of mydb [optional]. This argument must be provided
73 if not ``persistence`` is passed
74 persistence (WimPersistence): High-level data storage abstraction
75 [optional]. If this argument is not present, ``db`` must be.
76 engine (WimEngine): Implementation of the business logic
77 for the engine of WAN networks
78 logger (logging.Logger): logger object [optional]
79 url_base(str): Path fragment to be prepended to the routes [optional]
80 plugins(list): List of bottle plugins to be applied to routes
81 [optional]
82 """
83 def __init__(self, db=None, persistence=None, engine=None,
84 url_base='', logger=None, plugins=()):
85 self.persist = persistence or WimPersistence(db)
86 self.engine = engine or WimEngine(self.persist)
87 self.url_base = url_base
88 self.logger = logger or logging.getLogger('openmano.wim.http')
89 error_handler = ErrorHandler(self.logger)
90 self.plugins = [error_handler] + list(plugins)
91
92 @route('GET', '/<tenant_id>/wims')
93 def http_list_wims(self, tenant_id):
94 allowed_fields = ('uuid', 'name', 'wim_url', 'type', 'created_at')
95 select_, where_, limit_ = filter_query_string(
96 request.query, None, allowed_fields)
97 # ^ Since we allow the user to customize the db query using the HTTP
98 # query and it is quite difficult to re-use this query, let's just
99 # do a ad-hoc call to the db
100
101 from_ = 'wims'
102 if tenant_id != 'any':
103 where_['nfvo_tenant_id'] = tenant_id
104 if 'created_at' in select_:
105 select_[select_.index('created_at')] = (
106 'w.created_at as created_at')
107 if 'created_at' in where_:
108 where_['w.created_at'] = where_.pop('created_at')
109 from_ = ('wims as w join wim_nfvo_tenants as wt '
110 'on w.uuid=wt.wim_id')
111
112 wims = self.persist.query(
113 FROM=from_, SELECT=select_, WHERE=where_, LIMIT=limit_,
114 error_if_none=False)
115
116 utils.convert_float_timestamp2str(wims)
117 return format_out({'wims': wims})
118
119 @route('GET', '/<tenant_id>/wims/<wim_id>')
120 def http_get_wim(self, tenant_id, wim_id):
121 tenant_id = None if tenant_id == 'any' else tenant_id
122 wim = self.engine.get_wim(wim_id, tenant_id)
123 mappings = self.engine.get_wim_port_mappings(wim_id)
124 wim['config'] = utils.merge_dicts(wim.get('config', {}) or {},
125 wim_port_mapping=mappings)
126 return format_out({'wim': wim})
127
128 @route('POST', '/wims')
129 def http_create_wim(self):
130 http_content, _ = format_in(wim_schema, confidential_data=True)
131 r = utils.remove_extra_items(http_content, wim_schema)
132 if r:
133 self.logger.debug("Remove extra items received %r", r)
134 data = self.engine.create_wim(http_content['wim'])
135 return self.http_get_wim('any', data)
136
137 @route('PUT', '/wims/<wim_id>')
138 def http_update_wim(self, wim_id):
139 '''edit wim details, can use both uuid or name'''
140 # parse input data
141 http_content, _ = format_in(wim_edit_schema)
142 r = utils.remove_extra_items(http_content, wim_edit_schema)
143 if r:
144 self.logger.debug("Remove received extra items %s", r)
145
146 wim_id = self.engine.update_wim(wim_id, http_content['wim'])
147 return self.http_get_wim('any', wim_id)
148
149 @route('DELETE', '/wims/<wim_id>')
150 def http_delete_wim(self, wim_id):
151 """Delete a wim from a database, can use both uuid or name"""
152 data = self.engine.delete_wim(wim_id)
153 # TODO Remove WIM in orchestrator
154 return format_out({"result": "wim '" + data + "' deleted"})
155
156 @route('POST', '/<tenant_id>/wims/<wim_id>')
157 def http_create_wim_account(self, tenant_id, wim_id):
158 """Associate an existing wim to this tenant"""
159 # parse input data
160 http_content, _ = format_in(
161 wim_account_schema, confidential_data=True)
162 removed = utils.remove_extra_items(http_content, wim_account_schema)
163 removed and self.logger.debug("Remove extra items %r", removed)
164 account = self.engine.create_wim_account(
165 wim_id, tenant_id, http_content['wim_account'])
166 # check update succeeded
167 return format_out({"wim_account": account})
168
169 @route('PUT', '/<tenant_id>/wims/<wim_id>')
170 def http_update_wim_accounts(self, tenant_id, wim_id):
171 """Edit the association of an existing wim to this tenant"""
172 tenant_id = None if tenant_id == 'any' else tenant_id
173 # parse input data
174 http_content, _ = format_in(
175 wim_account_schema, confidential_data=True)
176 removed = utils.remove_extra_items(http_content, wim_account_schema)
177 removed and self.logger.debug("Remove extra items %r", removed)
178 accounts = self.engine.update_wim_accounts(
179 wim_id, tenant_id, http_content['wim_account'])
180
181 if tenant_id:
182 return format_out({'wim_account': accounts[0]})
183
184 return format_out({'wim_accounts': accounts})
185
186 @route('DELETE', '/<tenant_id>/wims/<wim_id>')
187 def http_delete_wim_accounts(self, tenant_id, wim_id):
188 """Deassociate an existing wim to this tenant"""
189 tenant_id = None if tenant_id == 'any' else tenant_id
190 accounts = self.engine.delete_wim_accounts(wim_id, tenant_id,
191 error_if_none=True)
192
193 properties = (
194 (account['name'], wim_id,
195 utils.safe_get(account, 'association.nfvo_tenant_id', tenant_id))
196 for account in accounts)
197
198 return format_out({
199 'result': '\n'.join('WIM account `{}` deleted. '
200 'Tenant `{}` detached from WIM `{}`'
201 .format(*p) for p in properties)
202 })
203
204 @route('POST', '/<tenant_id>/wims/<wim_id>/port_mapping')
205 def http_create_wim_port_mappings(self, tenant_id, wim_id):
206 """Set the wim port mapping for a wim"""
207 # parse input data
208 http_content, _ = format_in(wim_port_mapping_schema)
209
210 data = self.engine.create_wim_port_mappings(
211 wim_id, http_content['wim_port_mapping'], tenant_id)
212 return format_out({"wim_port_mapping": data})
213
214 @route('GET', '/<tenant_id>/wims/<wim_id>/port_mapping')
215 def http_get_wim_port_mappings(self, tenant_id, wim_id):
216 """Get wim port mapping details"""
217 # TODO: tenant_id is never used, so it should be removed
218 data = self.engine.get_wim_port_mappings(wim_id)
219 return format_out({"wim_port_mapping": data})
220
221 @route('DELETE', '/<tenant_id>/wims/<wim_id>/port_mapping')
222 def http_delete_wim_port_mappings(self, tenant_id, wim_id):
223 """Clean wim port mapping"""
224 # TODO: tenant_id is never used, so it should be removed
225 data = self.engine.delete_wim_port_mappings(wim_id)
226 return format_out({"result": data})