1 # -*- coding: utf-8 -*-
3 # Copyright 2018 University of Bristol - High Performance Networks Research
7 # Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
8 # Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
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
14 # http://www.apache.org/licenses/LICENSE-2.0
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
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact with: <highperformance-networks@bristol.ac.uk>
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.
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.
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.
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
43 Direct domain/persistence logic should be avoided in this file, instead
44 calls to other layers should be done.
48 from bottle
import request
51 from ..http_tools
.errors
import ErrorHandler
52 from ..http_tools
.handler
import BaseHandler
, route
53 from ..http_tools
.request_processing
import (
58 from .engine
import WimEngine
59 from .persistence
import WimPersistence
60 from .schemas
import (
63 wim_port_mapping_schema
,
68 class WimHandler(BaseHandler
):
69 """HTTP route implementations for WIM related URLs
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
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
)
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
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')
112 wims
= self
.persist
.query(
113 FROM
=from_
, SELECT
=select_
, WHERE
=where_
, LIMIT
=limit_
,
116 utils
.convert_float_timestamp2str(wims
)
117 return format_out({'wims': wims
})
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
})
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
)
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
)
137 @route('PUT', '/wims/<wim_id>')
138 def http_update_wim(self
, wim_id
):
139 '''edit wim details, can use both uuid or name'''
141 http_content
, _
= format_in(wim_edit_schema
)
142 r
= utils
.remove_extra_items(http_content
, wim_edit_schema
)
144 self
.logger
.debug("Remove received extra items %s", r
)
146 wim_id
= self
.engine
.update_wim(wim_id
, http_content
['wim'])
147 return self
.http_get_wim('any', wim_id
)
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"})
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"""
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
})
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
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'])
182 return format_out({'wim_account': accounts
[0]})
184 return format_out({'wim_accounts': accounts
})
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
,
194 (account
['name'], wim_id
,
195 utils
.safe_get(account
, 'association.nfvo_tenant_id', tenant_id
))
196 for account
in accounts
)
199 'result': '\n'.join('WIM account `{}` deleted. '
200 'Tenant `{}` detached from WIM `{}`'
201 .format(*p
) for p
in properties
)
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"""
208 http_content
, _
= format_in(wim_port_mapping_schema
)
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
})
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
})
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
})