# Copyright (c) 2014 VMware, Inc. All rights reserved. # # 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. # from __future__ import print_function from __future__ import division from __future__ import absolute_import from oslo_log import log as logging from congress.api import api_utils from congress.api import base from congress.api import webservice from congress import exception LOG = logging.getLogger(__name__) def d6service(name, keys, inbox, datapath, args): return RowModel(name, keys, inbox=inbox, dataPath=datapath, **args) class RowModel(base.APIModel): """Model for handling API requests about Rows.""" # TODO(thinrichs): No rows have IDs right now. Maybe eventually # could make ID the hash of the row, but then might as well # just make the ID a string repr of the row. No use case # for it as of now since all rows are read-only. # def get_item(self, id_, context=None): # """Retrieve item with id id_ from model. # Args: # id_: The ID of the item to retrieve # context: Key-values providing frame of reference of request # Returns: # The matching item or None if item with id_ does not exist. # """ # Note(thread-safety): blocking function def get_items(self, params, context=None): """Get items in model. Args: params: A dict-like object containing parameters from the request query string and body. context: Key-values providing frame of reference of request Returns: A dict containing at least a 'results' key whose value is a list of items in the model. Additional keys set in the dict will also be rendered for the user. """ LOG.info("get_items(context=%s)", context) gen_trace = False if 'trace' in params and params['trace'].lower() == 'true': gen_trace = True # Get the caller, it should be either policy or datasource # Note(thread-safety): blocking call caller, source_id = api_utils.get_id_from_context( context, self.datasource_mgr, self.engine) # FIXME(threod-safety): in DSE2, the returned caller can be a # datasource name. But the datasource name may now refer to a new, # unrelated datasource. Causing the rest of this code to operate on # an unintended datasource. # It would have saved us if table_id was an UUID rather than a name, # but it appears that table_id is just another word for tablename. # Fix: check UUID of datasource before operating. Abort if mismatch table_id = context['table_id'] try: args = {'table_id': table_id, 'source_id': source_id, 'trace': gen_trace} if caller is self.engine: # allow extra time for row policy engine query # Note(thread-safety): blocking call result = self.invoke_rpc( caller, 'get_row_data', args, timeout=self.dse_long_timeout) else: # Note(thread-safety): blocking call result = self.invoke_rpc(caller, 'get_row_data', args) except exception.CongressException as e: m = ("Error occurred while processing source_id '%s' for row " "data of the table '%s'" % (source_id, table_id)) LOG.exception(m) raise webservice.DataModelException.create(e) if gen_trace and caller is self.engine: # DSE2 returns lists instead of tuples, so correct that. results = [{'data': tuple(x['data'])} for x in result[0]] return {'results': results, 'trace': result[1] or "Not available"} else: result = [{'data': tuple(x['data'])} for x in result] return {'results': result} # Note(thread-safety): blocking function def update_items(self, items, params, context=None): """Updates all data in a table. Args: id_: A table id for updating all row items: A data for new rows params: A dict-like object containing parameters from request query context: Key-values providing frame of reference of request Returns: None Raises: KeyError: table id doesn't exist DataModelException: any error occurs during replacing rows. """ LOG.info("update_items(context=%s)", context) # Note(thread-safety): blocking call caller, source_id = api_utils.get_id_from_context(context, self.datasource_mgr, self.engine) # FIXME(threod-safety): in DSE2, the returned caller can be a # datasource name. But the datasource name may now refer to a new, # unrelated datasource. Causing the rest of this code to operate on # an unintended datasource. # It would have saved us if table_id was an UUID rather than a name, # but it appears that table_id is just another word for tablename. # Fix: check UUID of datasource before operating. Abort if mismatch table_id = context['table_id'] try: args = {'table_id': table_id, 'source_id': source_id, 'objs': items} # Note(thread-safety): blocking call self.invoke_rpc(caller, 'update_entire_data', args) except exception.CongressException as e: LOG.exception("Error occurred while processing updating rows " "for source_id '%s' and table_id '%s'", source_id, table_id) raise webservice.DataModelException.create(e) LOG.info("finish update_items(context=%s)", context) LOG.debug("updated table %s with row items: %s", table_id, str(items)) # TODO(thinrichs): It makes sense to sometimes allow users to create # a new row for internal data sources. But since we don't have # those yet all tuples are read-only from the API. # def add_item(self, item, id_=None, context=None): # """Add item to model. # Args: # item: The item to add to the model # id_: The ID of the item, or None if an ID should be generated # context: Key-values providing frame of reference of request # Returns: # Tuple of (ID, newly_created_item) # Raises: # KeyError: ID already exists. # """ # TODO(thinrichs): once we have internal data sources, # add the ability to update a row. (Or maybe not and implement # via add+delete.) # def update_item(self, id_, item, context=None): # """Update item with id_ with new data. # Args: # id_: The ID of the item to be updated # item: The new item # context: Key-values providing frame of reference of request # Returns: # The updated item. # Raises: # KeyError: Item with specified id_ not present. # """ # # currently a noop since the owner_id cannot be changed # if id_ not in self.items: # raise KeyError("Cannot update item with ID '%s': " # "ID does not exist") # return item # TODO(thinrichs): once we can create, we should be able to delete # def delete_item(self, id_, context=None): # """Remove item from model. # Args: # id_: The ID of the item to be removed # context: Key-values providing frame of reference of request # Returns: # The removed item. # Raises: # KeyError: Item with specified id_ not present. # """