New Request/Result System.
Change-Id: I55c54bfa92eb339daf3f5b3683dcc33cbe0cee90
This commit is contained in:
parent
ca78c705e7
commit
5b652f9a1f
|
@ -30,6 +30,7 @@ from iotronic.api.controllers.v1 import enabledwebservice
|
||||||
from iotronic.api.controllers.v1 import fleet
|
from iotronic.api.controllers.v1 import fleet
|
||||||
from iotronic.api.controllers.v1 import plugin
|
from iotronic.api.controllers.v1 import plugin
|
||||||
from iotronic.api.controllers.v1 import port
|
from iotronic.api.controllers.v1 import port
|
||||||
|
from iotronic.api.controllers.v1 import request
|
||||||
from iotronic.api.controllers.v1 import service
|
from iotronic.api.controllers.v1 import service
|
||||||
from iotronic.api.controllers.v1 import webservice
|
from iotronic.api.controllers.v1 import webservice
|
||||||
|
|
||||||
|
@ -145,6 +146,14 @@ class V1(base.APIBase):
|
||||||
bookmark=True)
|
bookmark=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
v1.requests = [link.Link.make_link('self', pecan.request.public_url,
|
||||||
|
'requests', ''),
|
||||||
|
link.Link.make_link('bookmark',
|
||||||
|
pecan.request.public_url,
|
||||||
|
'requests', '',
|
||||||
|
bookmark=True)
|
||||||
|
]
|
||||||
|
|
||||||
return v1
|
return v1
|
||||||
|
|
||||||
|
|
||||||
|
@ -158,6 +167,7 @@ class Controller(rest.RestController):
|
||||||
ports = port.PortsController()
|
ports = port.PortsController()
|
||||||
fleets = fleet.FleetsController()
|
fleets = fleet.FleetsController()
|
||||||
webservices = webservice.WebservicesController()
|
webservices = webservice.WebservicesController()
|
||||||
|
requests = request.RequestsController()
|
||||||
|
|
||||||
@expose.expose(V1)
|
@expose.expose(V1)
|
||||||
def get(self):
|
def get(self):
|
||||||
|
|
|
@ -167,6 +167,7 @@ class FleetsController(rest.RestController):
|
||||||
|
|
||||||
def _get_fleets_collection(self, marker, limit,
|
def _get_fleets_collection(self, marker, limit,
|
||||||
sort_key, sort_dir,
|
sort_key, sort_dir,
|
||||||
|
project=None,
|
||||||
fields=None):
|
fields=None):
|
||||||
|
|
||||||
limit = api_utils.validate_limit(limit)
|
limit = api_utils.validate_limit(limit)
|
||||||
|
@ -183,6 +184,10 @@ class FleetsController(rest.RestController):
|
||||||
"sorting") % {'key': sort_key})
|
"sorting") % {'key': sort_key})
|
||||||
|
|
||||||
filters = {}
|
filters = {}
|
||||||
|
|
||||||
|
if project:
|
||||||
|
if pecan.request.context.is_admin:
|
||||||
|
filters['project_id'] = project
|
||||||
fleets = objects.Fleet.list(pecan.request.context, limit,
|
fleets = objects.Fleet.list(pecan.request.context, limit,
|
||||||
marker_obj,
|
marker_obj,
|
||||||
sort_key=sort_key, sort_dir=sort_dir,
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
@ -205,7 +210,7 @@ class FleetsController(rest.RestController):
|
||||||
|
|
||||||
rpc_fleet = api_utils.get_rpc_fleet(fleet_ident)
|
rpc_fleet = api_utils.get_rpc_fleet(fleet_ident)
|
||||||
cdict = pecan.request.context.to_policy_values()
|
cdict = pecan.request.context.to_policy_values()
|
||||||
cdict['project'] = rpc_fleet.project
|
cdict['project_id'] = rpc_fleet.project
|
||||||
policy.authorize('iot:fleet:get_one', cdict, cdict)
|
policy.authorize('iot:fleet:get_one', cdict, cdict)
|
||||||
|
|
||||||
return Fleet.convert_with_links(rpc_fleet, fields=fields)
|
return Fleet.convert_with_links(rpc_fleet, fields=fields)
|
||||||
|
@ -237,6 +242,7 @@ class FleetsController(rest.RestController):
|
||||||
fields = _DEFAULT_RETURN_FIELDS
|
fields = _DEFAULT_RETURN_FIELDS
|
||||||
return self._get_fleets_collection(marker,
|
return self._get_fleets_collection(marker,
|
||||||
limit, sort_key, sort_dir,
|
limit, sort_key, sort_dir,
|
||||||
|
project=cdict['project_id'],
|
||||||
fields=fields)
|
fields=fields)
|
||||||
|
|
||||||
@expose.expose(Fleet, body=Fleet, status_code=201)
|
@expose.expose(Fleet, body=Fleet, status_code=201)
|
||||||
|
|
|
@ -0,0 +1,296 @@
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
import pecan
|
||||||
|
from pecan import rest
|
||||||
|
import wsme
|
||||||
|
from wsme import types as wtypes
|
||||||
|
|
||||||
|
from iotronic.api.controllers import base
|
||||||
|
from iotronic.api.controllers import link
|
||||||
|
from iotronic.api.controllers.v1 import collection
|
||||||
|
from iotronic.api.controllers.v1.result import ResultCollection
|
||||||
|
from iotronic.api.controllers.v1 import types
|
||||||
|
from iotronic.api.controllers.v1 import utils as api_utils
|
||||||
|
from iotronic.api import expose
|
||||||
|
from iotronic.common import exception
|
||||||
|
from iotronic.common import policy
|
||||||
|
from iotronic import objects
|
||||||
|
|
||||||
|
_DEFAULT_RETURN_FIELDS = (
|
||||||
|
'uuid',
|
||||||
|
'destination_uuid',
|
||||||
|
'main_request_uuid',
|
||||||
|
'pending_requests',
|
||||||
|
'status',
|
||||||
|
'type',
|
||||||
|
'action'
|
||||||
|
)
|
||||||
|
|
||||||
|
_DEFAULT_RESULT_RETURN_FIELDS = (
|
||||||
|
'board_uuid',
|
||||||
|
'request_uuid',
|
||||||
|
'result',
|
||||||
|
'message'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Request(base.APIBase):
|
||||||
|
"""API representation of a request.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid = types.uuid
|
||||||
|
destination_uuid = types.uuid
|
||||||
|
main_request_uuid = types.uuid
|
||||||
|
pending_requests = wsme.types.IntegerType()
|
||||||
|
status = wsme.wsattr(wtypes.text)
|
||||||
|
project = types.uuid
|
||||||
|
type = wsme.types.IntegerType()
|
||||||
|
action = wsme.wsattr(wtypes.text)
|
||||||
|
|
||||||
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.fields = []
|
||||||
|
fields = list(objects.Request.fields)
|
||||||
|
for k in fields:
|
||||||
|
# Skip fields we do not expose.
|
||||||
|
if not hasattr(self, k):
|
||||||
|
continue
|
||||||
|
self.fields.append(k)
|
||||||
|
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_with_links(request, url, fields=None):
|
||||||
|
request_uuid = request.uuid
|
||||||
|
if fields is not None:
|
||||||
|
request.unset_fields_except(fields)
|
||||||
|
|
||||||
|
request.links = [link.Link.make_link('self', url, 'requests',
|
||||||
|
request_uuid),
|
||||||
|
link.Link.make_link('bookmark', url, 'requests',
|
||||||
|
request_uuid, bookmark=True)
|
||||||
|
]
|
||||||
|
return request
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_with_links(cls, rpc_request, fields=None):
|
||||||
|
request = Request(**rpc_request.as_dict())
|
||||||
|
|
||||||
|
if fields is not None:
|
||||||
|
api_utils.check_for_invalid_fields(fields, request.as_dict())
|
||||||
|
|
||||||
|
return cls._convert_with_links(request, pecan.request.public_url,
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestCollection(collection.Collection):
|
||||||
|
"""API representation of a collection of requests."""
|
||||||
|
|
||||||
|
requests = [Request]
|
||||||
|
"""A list containing requests objects"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self._type = 'requests'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_with_links(requests, limit, url=None, fields=None, **kwargs):
|
||||||
|
collection = RequestCollection()
|
||||||
|
collection.requests = [Request.convert_with_links(n, fields=fields)
|
||||||
|
for n in requests]
|
||||||
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||||
|
return collection
|
||||||
|
|
||||||
|
|
||||||
|
class ResultsRequestController(rest.RestController):
|
||||||
|
def __init__(self, request_ident):
|
||||||
|
self.request_ident = request_ident
|
||||||
|
|
||||||
|
@expose.expose(ResultCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype)
|
||||||
|
def get_all(self, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None):
|
||||||
|
"""Retrieve a list of boards.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('iot:result:get', cdict, cdict)
|
||||||
|
|
||||||
|
if fields is None:
|
||||||
|
fields = _DEFAULT_RESULT_RETURN_FIELDS
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
filters['request_uuid'] = self.request_ident
|
||||||
|
|
||||||
|
results = objects.Result.list(pecan.request.context, limit, marker,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
|
||||||
|
|
||||||
|
return ResultCollection.convert_with_links(results, limit,
|
||||||
|
fields=fields,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsController(rest.RestController):
|
||||||
|
"""REST controller for Requests."""
|
||||||
|
|
||||||
|
_subcontroller_map = {
|
||||||
|
'results': ResultsRequestController,
|
||||||
|
}
|
||||||
|
|
||||||
|
invalid_sort_key_list = ['extra', ]
|
||||||
|
|
||||||
|
_custom_actions = {
|
||||||
|
'detail': ['GET'],
|
||||||
|
}
|
||||||
|
|
||||||
|
@pecan.expose()
|
||||||
|
def _lookup(self, ident, *remainder):
|
||||||
|
try:
|
||||||
|
ident = types.uuid_or_name.validate(ident)
|
||||||
|
except exception.InvalidUuidOrName as e:
|
||||||
|
pecan.abort('400', e.args[0])
|
||||||
|
if not remainder:
|
||||||
|
return
|
||||||
|
|
||||||
|
subcontroller = self._subcontroller_map.get(remainder[0])
|
||||||
|
if subcontroller:
|
||||||
|
return subcontroller(request_ident=ident), remainder[1:]
|
||||||
|
|
||||||
|
def _get_requests_collection(self, marker, limit,
|
||||||
|
sort_key, sort_dir,
|
||||||
|
project=None,
|
||||||
|
fields=None):
|
||||||
|
|
||||||
|
limit = api_utils.validate_limit(limit)
|
||||||
|
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
|
marker_obj = None
|
||||||
|
if marker:
|
||||||
|
marker_obj = objects.Request.get_by_uuid(pecan.request.context,
|
||||||
|
marker)
|
||||||
|
|
||||||
|
if sort_key in self.invalid_sort_key_list:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
("The sort_key value %(key)s is an invalid field for "
|
||||||
|
"sorting") % {'key': sort_key})
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
if project:
|
||||||
|
if pecan.request.context.is_admin:
|
||||||
|
filters['project_id'] = project
|
||||||
|
requests = objects.Request.list(pecan.request.context, limit,
|
||||||
|
marker_obj,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
|
||||||
|
|
||||||
|
return RequestCollection.convert_with_links(requests, limit,
|
||||||
|
fields=fields,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
|
@expose.expose(Request, types.uuid_or_name, types.listtype)
|
||||||
|
def get_one(self, request_ident, fields=None):
|
||||||
|
"""Retrieve information about the given request.
|
||||||
|
|
||||||
|
:param request_ident: UUID or logical name of a request.
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('iot:request:get_one', cdict, cdict)
|
||||||
|
|
||||||
|
rpc_request = objects.Request.get_by_uuid(pecan.request.context,
|
||||||
|
request_ident)
|
||||||
|
return Request.convert_with_links(rpc_request, fields=fields)
|
||||||
|
|
||||||
|
@expose.expose(RequestCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||||
|
def get_all(self, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None):
|
||||||
|
"""Retrieve a list of requests.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param with_public: Optional boolean to get also public pluings.
|
||||||
|
:param all_requests: Optional boolean to get all the pluings.
|
||||||
|
Only for the admin
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('iot:request:get', cdict, cdict)
|
||||||
|
|
||||||
|
if fields is None:
|
||||||
|
fields = _DEFAULT_RETURN_FIELDS
|
||||||
|
return self._get_requests_collection(marker,
|
||||||
|
limit, sort_key, sort_dir,
|
||||||
|
project=cdict['project_id'],
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
|
@expose.expose(RequestCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||||
|
def detail(self, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None, with_public=False, all_requests=False):
|
||||||
|
"""Retrieve a list of requests.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param with_public: Optional boolean to get also public request.
|
||||||
|
:param all_requests: Optional boolean to get all the requests.
|
||||||
|
Only for the admin
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('iot:request:get', cdict, cdict)
|
||||||
|
|
||||||
|
# /detail should only work against collections
|
||||||
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
|
if parent != "requests":
|
||||||
|
raise exception.HTTPNotFound()
|
||||||
|
|
||||||
|
return self._get_requests_collection(marker,
|
||||||
|
limit, sort_key, sort_dir,
|
||||||
|
with_public=with_public,
|
||||||
|
all_requests=all_requests,
|
||||||
|
fields=fields)
|
|
@ -0,0 +1,214 @@
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
import pecan
|
||||||
|
from pecan import rest
|
||||||
|
import wsme
|
||||||
|
from wsme import types as wtypes
|
||||||
|
|
||||||
|
from iotronic.api.controllers import base
|
||||||
|
from iotronic.api.controllers import link
|
||||||
|
from iotronic.api.controllers.v1 import collection
|
||||||
|
from iotronic.api.controllers.v1 import types
|
||||||
|
from iotronic.api.controllers.v1 import utils as api_utils
|
||||||
|
from iotronic.api import expose
|
||||||
|
from iotronic.common import exception
|
||||||
|
from iotronic.common import policy
|
||||||
|
from iotronic import objects
|
||||||
|
|
||||||
|
_DEFAULT_RETURN_FIELDS = (
|
||||||
|
'board_uuid',
|
||||||
|
'request_uuid',
|
||||||
|
'result',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Result(base.APIBase):
|
||||||
|
"""API representation of a result.
|
||||||
|
|
||||||
|
"""
|
||||||
|
board_uuid = types.uuid
|
||||||
|
request_uuid = types.uuid
|
||||||
|
result = wsme.wsattr(wtypes.text)
|
||||||
|
message = wsme.wsattr(wtypes.text)
|
||||||
|
|
||||||
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.fields = []
|
||||||
|
fields = list(objects.Result.fields)
|
||||||
|
for k in fields:
|
||||||
|
# Skip fields we do not expose.
|
||||||
|
if not hasattr(self, k):
|
||||||
|
continue
|
||||||
|
self.fields.append(k)
|
||||||
|
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_with_links(result, fields=None):
|
||||||
|
if fields is not None:
|
||||||
|
result.unset_fields_except(fields)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_with_links(cls, rpc_result, fields=None):
|
||||||
|
result = Result(**rpc_result.as_dict())
|
||||||
|
|
||||||
|
if fields is not None:
|
||||||
|
api_utils.check_for_invalid_fields(fields, result.as_dict())
|
||||||
|
|
||||||
|
return cls._convert_with_links(result,
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
|
|
||||||
|
class ResultCollection(collection.Collection):
|
||||||
|
"""API representation of a collection of results."""
|
||||||
|
|
||||||
|
results = [Result]
|
||||||
|
"""A list containing results objects"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self._type = 'results'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_with_links(results, limit, url=None, fields=None, **kwargs):
|
||||||
|
collection = ResultCollection()
|
||||||
|
collection.results = [Result.convert_with_links(n, fields=fields)
|
||||||
|
for n in results]
|
||||||
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||||
|
return collection
|
||||||
|
|
||||||
|
|
||||||
|
class ResultsController(rest.RestController):
|
||||||
|
"""REST controller for Results."""
|
||||||
|
|
||||||
|
invalid_sort_key_list = ['extra', ]
|
||||||
|
|
||||||
|
_custom_actions = {
|
||||||
|
'detail': ['GET'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_results_collection(self, marker, limit,
|
||||||
|
sort_key, sort_dir,
|
||||||
|
project=None,
|
||||||
|
fields=None):
|
||||||
|
|
||||||
|
limit = api_utils.validate_limit(limit)
|
||||||
|
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
|
marker_obj = None
|
||||||
|
if marker:
|
||||||
|
marker_obj = objects.Result.get_by_uuid(pecan.result.context,
|
||||||
|
marker)
|
||||||
|
|
||||||
|
if sort_key in self.invalid_sort_key_list:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
("The sort_key value %(key)s is an invalid field for "
|
||||||
|
"sorting") % {'key': sort_key})
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
if project:
|
||||||
|
if pecan.result.context.is_admin:
|
||||||
|
filters['project_id'] = project
|
||||||
|
results = objects.Result.list(pecan.result.context, limit,
|
||||||
|
marker_obj,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
|
||||||
|
|
||||||
|
return ResultCollection.convert_with_links(results, limit,
|
||||||
|
fields=fields,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
|
@expose.expose(Result, types.uuid_or_name, types.listtype)
|
||||||
|
def get_one(self, result_ident, fields=None):
|
||||||
|
"""Retrieve information about the given result.
|
||||||
|
|
||||||
|
:param result_ident: UUID or logical name of a result.
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cdict = pecan.result.context.to_policy_values()
|
||||||
|
policy.authorize('iot:result:get_one', cdict, cdict)
|
||||||
|
|
||||||
|
rpc_result = objects.Result.get_by_uuid(pecan.result.context,
|
||||||
|
result_ident)
|
||||||
|
return Result.convert_with_links(rpc_result, fields=fields)
|
||||||
|
|
||||||
|
@expose.expose(ResultCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||||
|
def get_all(self, request_uuid, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None):
|
||||||
|
"""Retrieve a list of results.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param with_public: Optional boolean to get also public pluings.
|
||||||
|
:param all_results: Optional boolean to get all the pluings.
|
||||||
|
Only for the admin
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
cdict = pecan.result.context.to_policy_values()
|
||||||
|
policy.authorize('iot:result:get', cdict, cdict)
|
||||||
|
|
||||||
|
if fields is None:
|
||||||
|
fields = _DEFAULT_RETURN_FIELDS
|
||||||
|
return self._get_results_collection(marker,
|
||||||
|
limit, sort_key, sort_dir,
|
||||||
|
request_uuid=request_uuid,
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
|
@expose.expose(ResultCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||||
|
def detail(self, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None, with_public=False, all_results=False):
|
||||||
|
"""Retrieve a list of results.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param with_public: Optional boolean to get also public result.
|
||||||
|
:param all_results: Optional boolean to get all the results.
|
||||||
|
Only for the admin
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cdict = pecan.result.context.to_policy_values()
|
||||||
|
policy.authorize('iot:result:get', cdict, cdict)
|
||||||
|
|
||||||
|
# /detail should only work against collections
|
||||||
|
parent = pecan.result.path.split('/')[:-1][-1]
|
||||||
|
if parent != "results":
|
||||||
|
raise exception.HTTPNotFound()
|
||||||
|
|
||||||
|
return self._get_results_collection(marker,
|
||||||
|
limit, sort_key, sort_dir,
|
||||||
|
with_public=with_public,
|
||||||
|
all_results=all_results,
|
||||||
|
fields=fields)
|
|
@ -209,6 +209,24 @@ enabledwebservice_policies = [
|
||||||
description='Retrieve a EnabledWebservice record'),
|
description='Retrieve a EnabledWebservice record'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
request_policies = [
|
||||||
|
policy.RuleDefault('iot:request:get',
|
||||||
|
'rule:is_admin or rule:is_iot_member',
|
||||||
|
description='Retrieve Request records'),
|
||||||
|
policy.RuleDefault('iot:request:get_one',
|
||||||
|
'rule:is_admin or rule:is_iot_member',
|
||||||
|
description='Retrieve Request record'),
|
||||||
|
]
|
||||||
|
|
||||||
|
result_policies = [
|
||||||
|
policy.RuleDefault('iot:result:get',
|
||||||
|
'rule:is_admin or rule:is_iot_member',
|
||||||
|
description='Retrieve Request records'),
|
||||||
|
policy.RuleDefault('iot:result:get_one',
|
||||||
|
'rule:is_admin or rule:is_iot_member',
|
||||||
|
description='Retrieve Request record'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def list_policies():
|
def list_policies():
|
||||||
policies = (default_policies
|
policies = (default_policies
|
||||||
|
@ -221,6 +239,8 @@ def list_policies():
|
||||||
+ fleet_policies
|
+ fleet_policies
|
||||||
+ webservice_policies
|
+ webservice_policies
|
||||||
+ enabledwebservice_policies
|
+ enabledwebservice_policies
|
||||||
|
+ request_policies
|
||||||
|
+ result_policies
|
||||||
)
|
)
|
||||||
return policies
|
return policies
|
||||||
|
|
||||||
|
|
|
@ -21,18 +21,21 @@ except Exception:
|
||||||
# allow iotronic api to run also with python2.7
|
# allow iotronic api to run also with python2.7
|
||||||
import pickle as cpickle
|
import pickle as cpickle
|
||||||
|
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import oslo_messaging
|
||||||
|
|
||||||
|
from iotronic import objects
|
||||||
from iotronic.common import exception, designate
|
from iotronic.common import exception, designate
|
||||||
from iotronic.common import neutron
|
from iotronic.common import neutron
|
||||||
from iotronic.common import states
|
from iotronic.common import states
|
||||||
from iotronic.conductor.provisioner import Provisioner
|
from iotronic.conductor.provisioner import Provisioner
|
||||||
from iotronic import objects
|
|
||||||
from iotronic.objects import base as objects_base
|
from iotronic.objects import base as objects_base
|
||||||
from iotronic.wamp import wampmessage as wm
|
from iotronic.wamp import wampmessage as wm
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
import oslo_messaging
|
|
||||||
import random
|
|
||||||
import socket
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -41,11 +44,13 @@ serializer = objects_base.IotronicObjectSerializer()
|
||||||
Port = list()
|
Port = list()
|
||||||
|
|
||||||
|
|
||||||
# Method to compare two versions.
|
|
||||||
# Return 1 if v2 is smaller,
|
|
||||||
# -1 if v1 is smaller,
|
|
||||||
# 0 if equal
|
|
||||||
def versionCompare(v1, v2):
|
def versionCompare(v1, v2):
|
||||||
|
"""Method to compare two versions.
|
||||||
|
Return 1 if v2 is smaller,
|
||||||
|
-1 if v1 is smaller,
|
||||||
|
0 if equal
|
||||||
|
"""
|
||||||
|
|
||||||
v1_list = v1.split(".")[:3]
|
v1_list = v1.split(".")[:3]
|
||||||
v2_list = v2.split(".")[:3]
|
v2_list = v2.split(".")[:3]
|
||||||
i = 0
|
i = 0
|
||||||
|
@ -62,6 +67,35 @@ def versionCompare(v1, v2):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def new_req(ctx, board, type, action, main_req=None, pending_requests=0):
|
||||||
|
req_data = {
|
||||||
|
'destination_uuid': board.uuid,
|
||||||
|
'type': type,
|
||||||
|
'status': objects.request.PENDING,
|
||||||
|
'action': action,
|
||||||
|
'project': board.project,
|
||||||
|
'pending_requests': pending_requests
|
||||||
|
}
|
||||||
|
if main_req:
|
||||||
|
req_data['main_request_uuid'] = main_req
|
||||||
|
req = objects.Request(ctx, **req_data)
|
||||||
|
req.create()
|
||||||
|
return req
|
||||||
|
|
||||||
|
|
||||||
|
def new_res(ctx, board, req_uuid):
|
||||||
|
res_data = {
|
||||||
|
'board_uuid': board.uuid,
|
||||||
|
'request_uuid': req_uuid,
|
||||||
|
'result': objects.result.RUNNING,
|
||||||
|
'message': ""
|
||||||
|
}
|
||||||
|
|
||||||
|
res = objects.Result(ctx, **res_data)
|
||||||
|
res.create()
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def get_best_agent(ctx):
|
def get_best_agent(ctx):
|
||||||
agents = objects.WampAgent.list(ctx, filters={'online': True})
|
agents = objects.WampAgent.list(ctx, filters={'online': True})
|
||||||
LOG.debug('found %d Agent(s).', len(agents))
|
LOG.debug('found %d Agent(s).', len(agents))
|
||||||
|
@ -238,9 +272,10 @@ class ConductorEndpoint(object):
|
||||||
|
|
||||||
return serializer.serialize_entity(ctx, new_board)
|
return serializer.serialize_entity(ctx, new_board)
|
||||||
|
|
||||||
def execute_on_board(self, ctx, board_uuid, wamp_rpc_call, wamp_rpc_args):
|
def execute_on_board(self, ctx, board_uuid, wamp_rpc_call, wamp_rpc_args,
|
||||||
LOG.debug('Executing \"%s\" on the board: %s',
|
main_req=None):
|
||||||
wamp_rpc_call, board_uuid)
|
LOG.debug('Executing \"%s\" on the board: %s (req %s)',
|
||||||
|
wamp_rpc_call, board_uuid, main_req)
|
||||||
|
|
||||||
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
||||||
# session should be taken from board, not from the object
|
# session should be taken from board, not from the object
|
||||||
|
@ -250,28 +285,12 @@ class ConductorEndpoint(object):
|
||||||
session.session_id + "." + \
|
session.session_id + "." + \
|
||||||
board.uuid + "." + wamp_rpc_call
|
board.uuid + "." + wamp_rpc_call
|
||||||
|
|
||||||
# check the session; it rise an excpetion if session miss
|
|
||||||
if not board.is_online():
|
if not board.is_online():
|
||||||
raise exception.BoardNotConnected(board=board.uuid)
|
raise exception.BoardNotConnected(board=board.uuid)
|
||||||
|
|
||||||
req_data = {
|
req = new_req(ctx, board, objects.request.BOARD, wamp_rpc_call,
|
||||||
'destination_uuid': board_uuid,
|
main_req)
|
||||||
'type': objects.request.BOARD,
|
res = new_res(ctx, board, req.uuid)
|
||||||
'status': objects.request.PENDING,
|
|
||||||
'action': wamp_rpc_call
|
|
||||||
}
|
|
||||||
req = objects.Request(ctx, **req_data)
|
|
||||||
req.create()
|
|
||||||
|
|
||||||
res_data = {
|
|
||||||
'board_uuid': board_uuid,
|
|
||||||
'request_uuid': req.uuid,
|
|
||||||
'result': objects.result.RUNNING,
|
|
||||||
'message': ""
|
|
||||||
}
|
|
||||||
|
|
||||||
res = objects.Result(ctx, **res_data)
|
|
||||||
res.create()
|
|
||||||
|
|
||||||
cctx = self.wamp_agent_client.prepare(server=board.agent)
|
cctx = self.wamp_agent_client.prepare(server=board.agent)
|
||||||
|
|
||||||
|
@ -285,7 +304,7 @@ class ConductorEndpoint(object):
|
||||||
|
|
||||||
response = cctx.call(ctx, 's4t_invoke_wamp',
|
response = cctx.call(ctx, 's4t_invoke_wamp',
|
||||||
wamp_rpc_call=full_wamp_call,
|
wamp_rpc_call=full_wamp_call,
|
||||||
req_uuid=req.uuid,
|
req=req,
|
||||||
data=wamp_rpc_args)
|
data=wamp_rpc_args)
|
||||||
|
|
||||||
response = wm.deserialize(response)
|
response = wm.deserialize(response)
|
||||||
|
@ -297,6 +316,13 @@ class ConductorEndpoint(object):
|
||||||
req.status = objects.request.COMPLETED
|
req.status = objects.request.COMPLETED
|
||||||
req.save()
|
req.save()
|
||||||
|
|
||||||
|
if req.main_request_uuid:
|
||||||
|
mreq = objects.Request.get_by_uuid(ctx, req.main_request_uuid)
|
||||||
|
mreq.pending_requests = mreq.pending_requests - 1
|
||||||
|
if mreq.pending_requests == 0:
|
||||||
|
mreq.status = objects.request.COMPLETED
|
||||||
|
mreq.save()
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def action_board(self, ctx, board_uuid, action, params):
|
def action_board(self, ctx, board_uuid, action, params):
|
||||||
|
@ -514,6 +540,8 @@ class ConductorEndpoint(object):
|
||||||
board_uuid)
|
board_uuid)
|
||||||
|
|
||||||
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
||||||
|
if not board.is_online():
|
||||||
|
raise exception.BoardNotConnected(board=board.uuid)
|
||||||
port_iotronic = objects.Port(ctx)
|
port_iotronic = objects.Port(ctx)
|
||||||
|
|
||||||
subnet_info = neutron.subnet_info(subnet_uuid)
|
subnet_info = neutron.subnet_info(subnet_uuid)
|
||||||
|
@ -641,6 +669,10 @@ class ConductorEndpoint(object):
|
||||||
LOG.debug('Creating webservice %s',
|
LOG.debug('Creating webservice %s',
|
||||||
newwbs.name)
|
newwbs.name)
|
||||||
|
|
||||||
|
board = objects.Board.get_by_uuid(ctx, newwbs.board_uuid)
|
||||||
|
if not board.is_online():
|
||||||
|
raise exception.BoardNotConnected(board=board.uuid)
|
||||||
|
|
||||||
en_webservice = objects.enabledwebservice. \
|
en_webservice = objects.enabledwebservice. \
|
||||||
EnabledWebservice.get_by_board_uuid(ctx,
|
EnabledWebservice.get_by_board_uuid(ctx,
|
||||||
newwbs.board_uuid)
|
newwbs.board_uuid)
|
||||||
|
@ -648,8 +680,6 @@ class ConductorEndpoint(object):
|
||||||
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
|
full_zone_domain = en_webservice.dns + "." + en_webservice.zone
|
||||||
dns_domain = newwbs.name + "." + full_zone_domain
|
dns_domain = newwbs.name + "." + full_zone_domain
|
||||||
|
|
||||||
board = objects.Board.get_by_uuid(ctx, newwbs.board_uuid)
|
|
||||||
|
|
||||||
create_record_dns_webservice(ctx, board, newwbs.name,
|
create_record_dns_webservice(ctx, board, newwbs.name,
|
||||||
en_webservice.dns,
|
en_webservice.dns,
|
||||||
en_webservice.zone)
|
en_webservice.zone)
|
||||||
|
@ -691,8 +721,13 @@ class ConductorEndpoint(object):
|
||||||
def destroy_webservice(self, ctx, webservice_id):
|
def destroy_webservice(self, ctx, webservice_id):
|
||||||
LOG.info('Destroying webservice with id %s',
|
LOG.info('Destroying webservice with id %s',
|
||||||
webservice_id)
|
webservice_id)
|
||||||
|
|
||||||
wbsrv = objects.Webservice.get_by_uuid(ctx, webservice_id)
|
wbsrv = objects.Webservice.get_by_uuid(ctx, webservice_id)
|
||||||
|
|
||||||
|
board = objects.Board.get_by_uuid(ctx, wbsrv.board_uuid)
|
||||||
|
if not board.is_online():
|
||||||
|
raise exception.BoardNotConnected(board=board.uuid)
|
||||||
|
|
||||||
en_webservice = objects.enabledwebservice. \
|
en_webservice = objects.enabledwebservice. \
|
||||||
EnabledWebservice.get_by_board_uuid(ctx,
|
EnabledWebservice.get_by_board_uuid(ctx,
|
||||||
wbsrv.board_uuid)
|
wbsrv.board_uuid)
|
||||||
|
@ -721,7 +756,6 @@ class ConductorEndpoint(object):
|
||||||
except exception:
|
except exception:
|
||||||
return exception
|
return exception
|
||||||
|
|
||||||
board = objects.Board.get_by_uuid(ctx, wbsrv.board_uuid)
|
|
||||||
if board.agent == None:
|
if board.agent == None:
|
||||||
raise exception.BoardInvalidStatus(uuid=board.uuid,
|
raise exception.BoardInvalidStatus(uuid=board.uuid,
|
||||||
status=board.status)
|
status=board.status)
|
||||||
|
@ -739,10 +773,16 @@ class ConductorEndpoint(object):
|
||||||
def enable_webservice(self, ctx, dns, zone, email, board_uuid):
|
def enable_webservice(self, ctx, dns, zone, email, board_uuid):
|
||||||
|
|
||||||
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
||||||
|
if not board.is_online():
|
||||||
|
raise exception.BoardNotConnected(board=board.uuid)
|
||||||
|
|
||||||
if board.agent == None:
|
if board.agent == None:
|
||||||
raise exception.BoardInvalidStatus(uuid=board.uuid,
|
raise exception.BoardInvalidStatus(uuid=board.uuid,
|
||||||
status=board.status)
|
status=board.status)
|
||||||
|
|
||||||
|
mreq = new_req(ctx, board, objects.request.BOARD,
|
||||||
|
"enable_webservice", pending_requests=3)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
create_record_dns(ctx, board, dns, zone)
|
create_record_dns(ctx, board, dns, zone)
|
||||||
except exception:
|
except exception:
|
||||||
|
@ -784,7 +824,7 @@ class ConductorEndpoint(object):
|
||||||
service = objects.Service.get_by_name(ctx, 'webservice')
|
service = objects.Service.get_by_name(ctx, 'webservice')
|
||||||
|
|
||||||
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
|
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
|
||||||
(service, http_port))
|
(service, http_port,), main_req=mreq.uuid)
|
||||||
result = manage_result(res, "ServiceEnable", board.uuid)
|
result = manage_result(res, "ServiceEnable", board.uuid)
|
||||||
|
|
||||||
exp_data = {
|
exp_data = {
|
||||||
|
@ -800,7 +840,7 @@ class ConductorEndpoint(object):
|
||||||
service = objects.Service.get_by_name(ctx, 'webservice_ssl')
|
service = objects.Service.get_by_name(ctx, 'webservice_ssl')
|
||||||
|
|
||||||
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
|
res = self.execute_on_board(ctx, board.uuid, "ServiceEnable",
|
||||||
(service, https_port))
|
(service, https_port,), main_req=mreq.uuid)
|
||||||
result = manage_result(res, "ServiceEnable", board.uuid)
|
result = manage_result(res, "ServiceEnable", board.uuid)
|
||||||
|
|
||||||
exp_data = {
|
exp_data = {
|
||||||
|
@ -822,11 +862,11 @@ class ConductorEndpoint(object):
|
||||||
board.uuid, dns, email)
|
board.uuid, dns, email)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# result =
|
|
||||||
self.execute_on_board(ctx,
|
self.execute_on_board(ctx,
|
||||||
board.uuid,
|
board.uuid,
|
||||||
'EnableWebService',
|
'EnableWebService',
|
||||||
(dns + "." + zone, email,))
|
(dns + "." + zone, email,),
|
||||||
|
main_req=mreq.uuid)
|
||||||
except exception:
|
except exception:
|
||||||
return exception
|
return exception
|
||||||
|
|
||||||
|
@ -840,11 +880,16 @@ class ConductorEndpoint(object):
|
||||||
board_uuid)
|
board_uuid)
|
||||||
|
|
||||||
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
board = objects.Board.get_by_uuid(ctx, board_uuid)
|
||||||
|
if not board.is_online():
|
||||||
|
raise exception.BoardNotConnected(board=board.uuid)
|
||||||
|
|
||||||
if board.agent == None:
|
if board.agent == None:
|
||||||
raise exception.BoardInvalidStatus(uuid=board.uuid,
|
raise exception.BoardInvalidStatus(uuid=board.uuid,
|
||||||
status=board.status)
|
status=board.status)
|
||||||
|
|
||||||
|
mreq = new_req(ctx, board, objects.request.BOARD,
|
||||||
|
"disable_webservice", pending_requests=3)
|
||||||
|
|
||||||
en_webservice = objects.enabledwebservice. \
|
en_webservice = objects.enabledwebservice. \
|
||||||
EnabledWebservice.get_by_board_uuid(ctx,
|
EnabledWebservice.get_by_board_uuid(ctx,
|
||||||
board.uuid)
|
board.uuid)
|
||||||
|
@ -862,7 +907,7 @@ class ConductorEndpoint(object):
|
||||||
service.uuid)
|
service.uuid)
|
||||||
|
|
||||||
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
|
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
|
||||||
(service,))
|
(service,), main_req=mreq.uuid)
|
||||||
LOG.debug(res.message)
|
LOG.debug(res.message)
|
||||||
exposed.destroy()
|
exposed.destroy()
|
||||||
|
|
||||||
|
@ -872,10 +917,19 @@ class ConductorEndpoint(object):
|
||||||
board_uuid,
|
board_uuid,
|
||||||
service.uuid)
|
service.uuid)
|
||||||
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
|
res = self.execute_on_board(ctx, board.uuid, "ServiceDisable",
|
||||||
(service,))
|
(service,), main_req=mreq.uuid)
|
||||||
LOG.debug(res.message)
|
LOG.debug(res.message)
|
||||||
exposed.destroy()
|
exposed.destroy()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.execute_on_board(ctx,
|
||||||
|
board.uuid,
|
||||||
|
'DisableWebService',
|
||||||
|
(),
|
||||||
|
main_req=mreq.uuid)
|
||||||
|
except exception:
|
||||||
|
return exception
|
||||||
|
|
||||||
webservice = objects.EnabledWebservice.get_by_board_uuid(
|
webservice = objects.EnabledWebservice.get_by_board_uuid(
|
||||||
ctx, board_uuid)
|
ctx, board_uuid)
|
||||||
|
|
||||||
|
|
|
@ -737,6 +737,28 @@ class Connection(object):
|
||||||
:returns: A result.
|
:returns: A result.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_result_list(self, filters=None, limit=None, marker=None,
|
||||||
|
sort_key=None, sort_dir=None):
|
||||||
|
"""Return a list of boards.
|
||||||
|
|
||||||
|
:param filters: Filters to apply. Defaults to None.
|
||||||
|
|
||||||
|
:associated: True | False
|
||||||
|
:reserved: True | False
|
||||||
|
:maintenance: True | False
|
||||||
|
:provision_state: provision state of board
|
||||||
|
:provisioned_before:
|
||||||
|
boards with provision_updated_at field before this
|
||||||
|
interval in seconds
|
||||||
|
:param limit: Maximum number of boards to return.
|
||||||
|
:param marker: the last item of the previous page; we return the next
|
||||||
|
result set.
|
||||||
|
:param sort_key: Attribute by which results should be sorted.
|
||||||
|
:param sort_dir: direction in which results should be sorted.
|
||||||
|
(asc, desc)
|
||||||
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def update_result(self, result_id, values):
|
def update_result(self, result_id, values):
|
||||||
"""Update properties of a result.
|
"""Update properties of a result.
|
||||||
|
@ -755,10 +777,10 @@ class Connection(object):
|
||||||
:returns: A request.
|
:returns: A request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
# @abc.abstractmethod
|
||||||
def get_results(self, request_uuid):
|
# def get_results(self, request_uuid):
|
||||||
"""get results of a request.
|
# """get results of a request.
|
||||||
|
#
|
||||||
:param request_uuid: the request_uuid.
|
# :param request_uuid: the request_uuid.
|
||||||
:returns: a list of results
|
# :returns: a list of results
|
||||||
"""
|
# """
|
||||||
|
|
|
@ -26,11 +26,18 @@ def upgrade():
|
||||||
sa.Column('uuid', sa.String(length=36), nullable=False),
|
sa.Column('uuid', sa.String(length=36), nullable=False),
|
||||||
sa.Column('destination_uuid', sa.String(length=36),
|
sa.Column('destination_uuid', sa.String(length=36),
|
||||||
nullable=False),
|
nullable=False),
|
||||||
|
sa.Column('main_request_uuid', sa.String(length=36),
|
||||||
|
nullable=True),
|
||||||
|
sa.Column('pending_requests', sa.Integer(), default=0,
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('project', sa.String(length=36), nullable=True),
|
||||||
sa.Column('status', sa.String(length=10), nullable=False),
|
sa.Column('status', sa.String(length=10), nullable=False),
|
||||||
sa.Column('type', sa.Integer(), nullable=False),
|
sa.Column('type', sa.Integer(), nullable=False),
|
||||||
sa.Column('action', sa.String(length=20), nullable=False),
|
sa.Column('action', sa.String(length=20), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.PrimaryKeyConstraint('id'),
|
||||||
sa.UniqueConstraint('uuid', name='uniq_requests0uuid')
|
sa.UniqueConstraint('uuid', name='uniq_requests0uuid'),
|
||||||
|
sa.ForeignKeyConstraint(['main_request_uuid'],
|
||||||
|
['requests.uuid'], )
|
||||||
)
|
)
|
||||||
|
|
||||||
op.create_table('results',
|
op.create_table('results',
|
||||||
|
|
|
@ -196,8 +196,18 @@ class Connection(api.Connection):
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = []
|
||||||
|
|
||||||
if 'project' in filters:
|
if 'project_id' in filters:
|
||||||
query = query.filter(models.Fleet.project == filters['project'])
|
query = query.filter(models.Fleet.project ==
|
||||||
|
filters['project_id'])
|
||||||
|
return query
|
||||||
|
|
||||||
|
def _add_requests_filters(self, query, filters):
|
||||||
|
if filters is None:
|
||||||
|
filters = []
|
||||||
|
|
||||||
|
if 'project_id' in filters:
|
||||||
|
query = query.filter(models.Request.project ==
|
||||||
|
filters['project_id'])
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def _add_wampagents_filters(self, query, filters):
|
def _add_wampagents_filters(self, query, filters):
|
||||||
|
@ -227,12 +237,25 @@ class Connection(api.Connection):
|
||||||
filter(models.Port.board_uuid == filters['board_uuid'])
|
filter(models.Port.board_uuid == filters['board_uuid'])
|
||||||
|
|
||||||
def _add_result_filters(self, query, filters):
|
def _add_result_filters(self, query, filters):
|
||||||
|
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = []
|
||||||
|
|
||||||
if 'result' in filters:
|
if 'result' in filters:
|
||||||
query = query.filter(models.Result.result == filters['result'])
|
query = query.filter(models.Result.result == filters['result'])
|
||||||
|
|
||||||
|
if 'request_uuid' in filters:
|
||||||
|
query = query.filter(
|
||||||
|
or_(
|
||||||
|
models.Request.main_request_uuid ==
|
||||||
|
filters['request_uuid'],
|
||||||
|
models.Request.uuid ==
|
||||||
|
filters['request_uuid']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
query = query.filter(models.Request.uuid ==
|
||||||
|
models.Result.request_uuid)
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def _do_update_board(self, board_id, values):
|
def _do_update_board(self, board_id, values):
|
||||||
|
@ -1249,6 +1272,13 @@ class Connection(api.Connection):
|
||||||
raise exception.InvalidParameterValue(err=msg)
|
raise exception.InvalidParameterValue(err=msg)
|
||||||
return self._do_update_request(request_id, values)
|
return self._do_update_request(request_id, values)
|
||||||
|
|
||||||
|
def get_request_list(self, filters=None, limit=None, marker=None,
|
||||||
|
sort_key=None, sort_dir=None):
|
||||||
|
query = model_query(models.Request)
|
||||||
|
query = self._add_requests_filters(query, filters)
|
||||||
|
return _paginate_query(models.Request, limit, marker,
|
||||||
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
# RESULT
|
# RESULT
|
||||||
|
|
||||||
def _do_update_result(self, update_id, values):
|
def _do_update_result(self, update_id, values):
|
||||||
|
@ -1281,11 +1311,18 @@ class Connection(api.Connection):
|
||||||
def update_result(self, result_id, values):
|
def update_result(self, result_id, values):
|
||||||
return self._do_update_result(result_id, values)
|
return self._do_update_result(result_id, values)
|
||||||
|
|
||||||
def get_results(self, request_uuid, filters=None):
|
def get_result_list(self, filters=None, limit=None, marker=None,
|
||||||
query = model_query(models.Result).filter_by(
|
sort_key=None, sort_dir=None):
|
||||||
request_uuid=request_uuid)
|
query = model_query(models.Result)
|
||||||
query = self._add_result_filters(query, filters)
|
query = self._add_result_filters(query, filters)
|
||||||
try:
|
return _paginate_query(models.Result, limit, marker,
|
||||||
return query.all()
|
sort_key, sort_dir, query)
|
||||||
except NoResultFound:
|
|
||||||
raise exception.ResultNotFound()
|
# def get_results(self, request_uuid, filters=None):
|
||||||
|
# query = model_query(models.Result).filter_by(
|
||||||
|
# request_uuid=request_uuid)
|
||||||
|
# query = self._add_result_filters(query, filters)
|
||||||
|
# try:
|
||||||
|
# return query.all()
|
||||||
|
# except NoResultFound:
|
||||||
|
# raise exception.ResultNotFound()
|
||||||
|
|
|
@ -14,20 +14,20 @@
|
||||||
"""
|
"""
|
||||||
SQLAlchemy models for iot data.
|
SQLAlchemy models for iot data.
|
||||||
"""
|
"""
|
||||||
|
from iotronic.common import paths
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_db.sqlalchemy import models
|
from oslo_db.sqlalchemy import models
|
||||||
import six.moves.urllib.parse as urlparse
|
|
||||||
from sqlalchemy import Boolean
|
from sqlalchemy import Boolean
|
||||||
from sqlalchemy import Column
|
from sqlalchemy import Column
|
||||||
from sqlalchemy import ForeignKey, Integer
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy import ForeignKey, Integer
|
||||||
from sqlalchemy import schema
|
from sqlalchemy import schema
|
||||||
from sqlalchemy import String
|
from sqlalchemy import String
|
||||||
from sqlalchemy.types import TypeDecorator, TEXT
|
from sqlalchemy.types import TypeDecorator, TEXT
|
||||||
from iotronic.common import paths
|
import six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
|
|
||||||
sql_opts = [
|
sql_opts = [
|
||||||
cfg.StrOpt('mysql_engine',
|
cfg.StrOpt('mysql_engine',
|
||||||
|
@ -326,8 +326,12 @@ class Request(Base):
|
||||||
table_args())
|
table_args())
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
uuid = Column(String(36))
|
uuid = Column(String(36))
|
||||||
|
main_request_uuid = Column(String(36),
|
||||||
|
ForeignKey('requests.uuid'),
|
||||||
|
nullable=True)
|
||||||
destination_uuid = Column(String(36))
|
destination_uuid = Column(String(36))
|
||||||
|
pending_requests = Column(Integer, default=0)
|
||||||
|
project = Column(String(36))
|
||||||
status = Column(String(10))
|
status = Column(String(10))
|
||||||
type = Column(Integer)
|
type = Column(Integer)
|
||||||
action = Column(String(20))
|
action = Column(String(20))
|
||||||
|
|
|
@ -19,7 +19,6 @@ from oslo_utils import uuidutils
|
||||||
from iotronic.common import exception
|
from iotronic.common import exception
|
||||||
from iotronic.db import api as db_api
|
from iotronic.db import api as db_api
|
||||||
from iotronic.objects import base
|
from iotronic.objects import base
|
||||||
from iotronic.objects.result import Result
|
|
||||||
from iotronic.objects import utils as obj_utils
|
from iotronic.objects import utils as obj_utils
|
||||||
|
|
||||||
BOARD = 0
|
BOARD = 0
|
||||||
|
@ -39,7 +38,10 @@ class Request(base.IotronicObject):
|
||||||
'id': int,
|
'id': int,
|
||||||
'uuid': obj_utils.str_or_none,
|
'uuid': obj_utils.str_or_none,
|
||||||
'destination_uuid': obj_utils.str_or_none,
|
'destination_uuid': obj_utils.str_or_none,
|
||||||
|
'main_request_uuid': obj_utils.str_or_none,
|
||||||
|
'pending_requests': int,
|
||||||
'status': obj_utils.str_or_none,
|
'status': obj_utils.str_or_none,
|
||||||
|
'project': obj_utils.str_or_none,
|
||||||
'type': int,
|
'type': int,
|
||||||
'action': obj_utils.str_or_none,
|
'action': obj_utils.str_or_none,
|
||||||
}
|
}
|
||||||
|
@ -88,15 +90,15 @@ class Request(base.IotronicObject):
|
||||||
request = Request._from_db_object(cls(context), db_request)
|
request = Request._from_db_object(cls(context), db_request)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@base.remotable_classmethod
|
# @base.remotable_classmethod
|
||||||
def get_results(cls, context, request_uuid, filters=None):
|
# def get_results(cls, context, filters=None):
|
||||||
"""Find a request based on uuid and return a Board object.
|
# """Find a request based on uuid and return a Board object.
|
||||||
|
#
|
||||||
:param uuid: the uuid of a request.
|
# :param uuid: the uuid of a request.
|
||||||
:returns: a :class:`Board` object.
|
# :returns: a :class:`Board` object.
|
||||||
"""
|
# """
|
||||||
return Result.get_results_list(context,
|
# return Result.get_results_list(context,
|
||||||
request_uuid, filters)
|
# filters)
|
||||||
|
|
||||||
# @base.remotable_classmethod
|
# @base.remotable_classmethod
|
||||||
# def get_results_request(cls,context,request_uuid):
|
# def get_results_request(cls,context,request_uuid):
|
||||||
|
@ -115,28 +117,29 @@ class Request(base.IotronicObject):
|
||||||
# request = Request._from_db_object(cls(context), db_request)
|
# request = Request._from_db_object(cls(context), db_request)
|
||||||
# return request
|
# return request
|
||||||
|
|
||||||
# @base.remotable_classmethod
|
@base.remotable_classmethod
|
||||||
# def list(cls, context, limit=None, marker=None, sort_key=None,
|
def list(cls, context, limit=None, marker=None, sort_key=None,
|
||||||
# sort_dir=None, filters=None):
|
sort_dir=None, filters=None):
|
||||||
# """Return a list of Request objects.
|
"""Return a list of Request objects.
|
||||||
#
|
|
||||||
# :param context: Security context.
|
:param context: Security context.
|
||||||
# :param limit: maximum number of resources to return in a
|
:param limit: maximum number of resources to return in a
|
||||||
# single result.
|
single result.
|
||||||
# :param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
# :param sort_key: column to sort results by.
|
:param sort_key: column to sort results by.
|
||||||
# :param sort_dir: direction to sort. "asc" or "desc".
|
:param sort_dir: direction to sort. "asc" or "desc".
|
||||||
# :param filters: Filters to apply.
|
:param filters: Filters to apply.
|
||||||
# :returns: a list of :class:`Request` object.
|
:returns: a list of :class:`Request` object.
|
||||||
#
|
|
||||||
# """
|
"""
|
||||||
# db_requests = cls.dbapi.get_request_list(filters=filters,
|
db_requests = cls.dbapi.get_request_list(filters=filters,
|
||||||
# limit=limit,
|
limit=limit,
|
||||||
# marker=marker,
|
marker=marker,
|
||||||
# sort_key=sort_key,
|
sort_key=sort_key,
|
||||||
# sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
# return [Request._from_db_object(cls(context), obj)
|
|
||||||
# for obj in db_requests]
|
return [Request._from_db_object(cls(context), obj)
|
||||||
|
for obj in db_requests]
|
||||||
|
|
||||||
@base.remotable
|
@base.remotable
|
||||||
def create(self, context=None):
|
def create(self, context=None):
|
||||||
|
|
|
@ -62,40 +62,41 @@ class Result(base.IotronicObject):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@base.remotable_classmethod
|
@base.remotable_classmethod
|
||||||
def get_results_list(cls, context, request_uuid, filters=None):
|
def get_results_list(cls, context, filters=None):
|
||||||
"""Find a result based on name and return a Board object.
|
"""Find a result based on name and return a Board object.
|
||||||
|
|
||||||
:param board_uuid: the board uuid result.
|
:param board_uuid: the board uuid result.
|
||||||
:param request_uuid: the request_uuid.
|
:param request_uuid: the request_uuid.
|
||||||
:returns: a :class:`result` object.
|
:returns: a :class:`result` object.
|
||||||
"""
|
"""
|
||||||
db_requests = cls.dbapi.get_results(request_uuid,
|
db_requests = cls.dbapi.get_result_list(
|
||||||
filters=filters)
|
filters=filters)
|
||||||
return [Result._from_db_object(cls(context), obj)
|
return [Result._from_db_object(cls(context), obj)
|
||||||
for obj in db_requests]
|
for obj in db_requests]
|
||||||
|
|
||||||
# @base.remotable_classmethod
|
@base.remotable_classmethod
|
||||||
# def list(cls, context, limit=None, marker=None, sort_key=None,
|
def list(cls, context, limit=None, marker=None, sort_key=None,
|
||||||
# sort_dir=None, filters=None):
|
sort_dir=None, filters=None):
|
||||||
# """Return a list of Result objects.
|
"""Return a list of Result objects.
|
||||||
#
|
|
||||||
# :param context: Security context.
|
:param context: Security context.
|
||||||
# :param limit: maximum number of resources to return in a
|
:param limit: maximum number of resources to return in a
|
||||||
# single result.
|
single result.
|
||||||
# :param marker: pagination marker for large data sets.
|
:param marker: pagination marker for large data sets.
|
||||||
# :param sort_key: column to sort results by.
|
:param sort_key: column to sort results by.
|
||||||
# :param sort_dir: direction to sort. "asc" or "desc".
|
:param sort_dir: direction to sort. "asc" or "desc".
|
||||||
# :param filters: Filters to apply.
|
:param filters: Filters to apply.
|
||||||
# :returns: a list of :class:`Result` object.
|
:returns: a list of :class:`Result` object.
|
||||||
#
|
|
||||||
# """
|
"""
|
||||||
# db_results = cls.dbapi.get_result_list(filters=filters,
|
|
||||||
# limit=limit,
|
db_results = cls.dbapi.get_result_list(filters=filters,
|
||||||
# marker=marker,
|
limit=limit,
|
||||||
# sort_key=sort_key,
|
marker=marker,
|
||||||
# sort_dir=sort_dir)
|
sort_key=sort_key,
|
||||||
# return [Result._from_db_object(cls(context), obj)
|
sort_dir=sort_dir)
|
||||||
# for obj in db_results]
|
return [Result._from_db_object(cls(context), obj)
|
||||||
|
for obj in db_results]
|
||||||
|
|
||||||
@base.remotable
|
@base.remotable
|
||||||
def create(self, context=None):
|
def create(self, context=None):
|
||||||
|
|
|
@ -96,12 +96,12 @@ connected = False
|
||||||
|
|
||||||
async def wamp_request(kwarg):
|
async def wamp_request(kwarg):
|
||||||
# for previous LR version (to be removed asap)
|
# for previous LR version (to be removed asap)
|
||||||
if 'req_uuid' in kwarg:
|
if 'req' in kwarg:
|
||||||
|
|
||||||
LOG.debug("calling: " + kwarg['wamp_rpc_call'] +
|
LOG.debug("calling: " + kwarg['wamp_rpc_call'] +
|
||||||
" with request id: " + kwarg['req_uuid'])
|
" with request id: " + kwarg['req']['uuid'])
|
||||||
d = await wamp_session_caller.call(kwarg['wamp_rpc_call'],
|
d = await wamp_session_caller.call(kwarg['wamp_rpc_call'],
|
||||||
kwarg['req_uuid'],
|
kwarg['req'],
|
||||||
*kwarg['data'])
|
*kwarg['data'])
|
||||||
else:
|
else:
|
||||||
LOG.debug("calling: " + kwarg['wamp_rpc_call'])
|
LOG.debug("calling: " + kwarg['wamp_rpc_call'])
|
||||||
|
|
|
@ -187,14 +187,20 @@ def notify_result(board_uuid, wampmessage):
|
||||||
res.message = wmsg.message
|
res.message = wmsg.message
|
||||||
res.save()
|
res.save()
|
||||||
|
|
||||||
filter = {"result": objects.result.RUNNING}
|
filter = {"result": objects.result.RUNNING,
|
||||||
|
"request_uuid": wmsg.req_id}
|
||||||
|
|
||||||
list_result = objects.Request.get_results(ctxt,
|
list_result = objects.Result.get_results_list(ctxt,
|
||||||
wmsg.req_id,
|
filter)
|
||||||
filter)
|
|
||||||
if len(list_result) == 0:
|
if len(list_result) == 0:
|
||||||
req = objects.Request.get_by_uuid(ctxt, wmsg.req_id)
|
req = objects.Request.get_by_uuid(ctxt, wmsg.req_id)
|
||||||
req.status = objects.request.COMPLETED
|
req.status = objects.request.COMPLETED
|
||||||
req.save()
|
req.save()
|
||||||
|
if req.main_request_uuid:
|
||||||
|
mreq = objects.Request.get_by_uuid(ctxt, req.main_request_uuid)
|
||||||
|
mreq.pending_requests = mreq.pending_requests - 1
|
||||||
|
if mreq.pending_requests == 0:
|
||||||
|
mreq.status = objects.request.COMPLETED
|
||||||
|
mreq.save()
|
||||||
|
|
||||||
return wm.WampSuccess('notification_received').serialize()
|
return wm.WampSuccess('notification_received').serialize()
|
||||||
|
|
Loading…
Reference in New Issue