def get_drone_mode_tvs(request_context, async_kvstore_client, user=None, device_ids=None): """Fetch list of tvs from the drone_mode_tvs collection""" params = None if not user: user = request_context.current_user if device_ids: urlsafe_device_ids = [ b64_to_urlsafe_b64(device_id) for device_id in device_ids ] query = build_containedin_clause(constants.KEY, urlsafe_device_ids) params = {constants.QUERY: json.dumps(query)} response = yield async_kvstore_client.async_kvstore_get_request( collection=constants.DRONE_MODE_TVS_COLLECTION_NAME, params=params, owner=user, auth_header=request_context.system_auth_header) yield check_and_raise_error(response, request_context, 'Get Drone Mode TVs') tvs = yield response.json() LOGGER.debug('get_drone_mode_tvs returned=%s', tvs) defer.returnValue(tvs)
def activate_tv_bookmark(request_context, async_client_factory, bookmark_name): """ Function to activate a TV bookmark :param request_context: request context used to make kvstore requests :param async_kvstore_client: async client used to make kvstore requests :param bookmark_name: tv bookmark name """ # check for bookmark with existing name async_kvstore_client = async_client_factory.kvstore_client() bookmark_json = yield fetch_tv_bookmark(bookmark_name, request_context, async_kvstore_client, error_if_empty=True) LOGGER.debug('bookmark_json used for activating tv bookmark=%s', bookmark_json) bookmark_json_items = bookmark_json.get(constants.TV_CONFIG_MAP, {}).items() # update timestamp when activating device_ids = set() for device_id, item in bookmark_json_items: item[constants.TIMESTAMP] = datetime.now().strftime('%s') device_ids.add(device_id) errors = yield validate_devices(device_ids, request_context, async_kvstore_client) # create deep copy of config data so original doesn't get modified post_data = deepcopy(bookmark_json_items) # remove device id and add key to dict for device_id, config in post_data: LOGGER.debug('device_id=%s, config=%s', device_id, config) if constants.DEVICE_ID in config: del config[constants.DEVICE_ID] kvstore_key = b64_to_urlsafe_b64(device_id) config[constants.KEY] = kvstore_key device_ids = [device_id for device_id, _ in post_data] data = [config for _, config in post_data] # Update drone mode tvs kvstore collection with tv config data yield async_kvstore_client.async_batch_save_request( request_context.auth_header, constants.DRONE_MODE_TVS_COLLECTION_NAME, data, owner=request_context.current_user) # Send config updates to all devices in the bookmark and # all ipads registered to the current user yield process_subscriptions(request_context, async_client_factory, tv_device_ids=device_ids) if errors: LOGGER.error( 'Errors occurred during TV Bookmark Activate Request for bookmark_name=%s, errors=%s', bookmark_name, errors) defer.returnValue(errors) LOGGER.info('Successful TV Bookmark Activate Request for bookmark_name=%s', bookmark_name) defer.returnValue(None)
def process_subscribe_drone_mode_ipad(request_context, client_subscription_message, server_subscription_response, async_client_factory): """ Process subscribe to drone mode ipad events for a device id :param request_context: Used to authenticate kvstore requests :param client_single_request: client request object protobuf :param single_server_response: server response object protobuf :param async_client factory: factory class used to generate kvstore and spacebridge clients """ LOGGER.debug('start of subscribe drone mode ipad') async_kvstore_client = async_client_factory.kvstore_client() LOGGER.debug('request_context=%s', request_context) # base64 encode device id device_id = request_context.device_id urlsafe_b64encoded_device_id = b64_to_urlsafe_b64(device_id) query = { constants.QUERY: json.dumps({ constants.DEVICE_ID: device_id, }), constants.LIMIT: 1 } # fetch device from registered_devices_table to get device name response = yield async_kvstore_client.async_kvstore_get_request( collection=constants.REGISTERED_DEVICES_COLLECTION_NAME, params=query, owner=request_context.current_user, auth_header=request_context.auth_header) yield check_and_raise_error(response, request_context, 'Fetch registered devices') device = yield response.json() if device: device = device[0] else: raise SpacebridgeApiRequestError('Invalid device id={}'.format(device_id), status_code=http.BAD_REQUEST) device_name = device[constants.DEVICE_NAME] # write to ipads collection ipad_json = {constants.DEVICE_NAME: device_name} response = yield async_kvstore_client.async_kvstore_post_or_update_request( constants.DRONE_MODE_IPADS_COLLECTION_NAME, json.dumps(ipad_json), request_context.system_auth_header, key_id=urlsafe_b64encoded_device_id, owner=request_context.current_user) # construct parameters for subscription creation / updating ttl_seconds = client_subscription_message.clientSubscribeRequest.ttlSeconds expiration_time = get_expiration_timestamp_str(ttl_seconds=ttl_seconds) now = get_current_timestamp_str() params = { constants.TTL_SECONDS: str(ttl_seconds), constants.DEVICE_ID: device_id, constants.LAST_UPDATE_TIME: now, constants.EXPIRED_TIME: expiration_time, constants.USER: request_context.current_user, constants.SUBSCRIPTION_TYPE: constants.DRONE_MODE_IPAD } # delete existing subscriptions query = { constants.QUERY: json.dumps({ constants.DEVICE_ID: device_id, constants.USER: request_context.current_user, constants.SUBSCRIPTION_TYPE: constants.DRONE_MODE_IPAD }) } delete_response = yield async_kvstore_client.async_kvstore_delete_request( collection=constants.SUBSCRIPTIONS_COLLECTION_NAME, params=query, owner=constants.NOBODY, auth_header=request_context.auth_header) yield check_and_raise_error(delete_response, request_context, 'Delete existing ipad subscriptions') # create subscription subscription_id = yield write_drone_mode_subscription(request_context, params, async_client_factory) server_subscription_response.subscriptionId = subscription_id server_subscription_response.serverSubscribeResponse.droneModeiPadSubscribe.SetInParent() LOGGER.debug('Successfully wrote drone mode ipad subscription with id=%s', subscription_id)
def process_tv_config_delete_request(request_context, client_single_request, single_server_response, async_client_factory): """This method will process a tvConfigDeleteRequest. :param request_context: Used to authenticate kvstore requests :param client_single_request: client request object protobuf :param single_server_response: server response object protobuf :param async_client factory: factory class used to generate kvstore and spacebridge clients """ async_kvstore_client = async_client_factory.kvstore_client() tv_config_delete_request = client_single_request.tvConfigDeleteRequest device_ids = tv_config_delete_request.deviceId LOGGER.debug('device_ids to delete=%s', device_ids) yield validate_devices(set(device_ids), request_context, async_kvstore_client) # need to fetch configs before deleting to make sure we're not deleting the captain # and orphaning the workers, if so, for each one that is a captain, # we need to fetch the other configs in the grid # delete the to be deleted captain from the device_id list, and update # the existing members of the grid tvs = yield get_drone_mode_tvs(request_context, async_kvstore_client, device_ids=device_ids) # fetch configs from kvstore # check to see if they are currently captain for tv in tvs: raw_id = base64.urlsafe_b64decode(str(tv[constants.KEY])) encoded_id = b64encode_to_str(raw_id) tv[constants.DEVICE_ID] = encoded_id if (has_grid(tv, is_json=True)): tv_config = TVConfig(**tv) grid_ids = tv.get(constants.TV_GRID, {}).get(constants.DEVICE_IDS, []) yield remove_from_grid(tv_config=tv_config, device_ids=grid_ids, request_context=request_context, async_kvstore_client=async_kvstore_client) entries_to_delete = [] for device_id in device_ids: kvstore_key = b64_to_urlsafe_b64(device_id) post_data = {constants.KEY: kvstore_key} entries_to_delete.append(post_data) deleted_ids = yield async_kvstore_client.async_batch_save_request( request_context.system_auth_header, constants.DRONE_MODE_TVS_COLLECTION_NAME, entries_to_delete, owner=request_context.current_user) deleted_device_ids = [urlsafe_b64_to_b64(deleted_id) for deleted_id in deleted_ids] if deleted_device_ids: single_server_response.tvConfigDeleteResponse.deletedIds.extend(deleted_device_ids) if not deleted_ids: raise SpacebridgeApiRequestError('None of the device_ids={} were deleted'.format(device_ids), status_code=http.INTERNAL_SERVER_ERROR) elif len(deleted_ids) != len(device_ids): kvstore_keys = {b64_to_urlsafe_b64(device_id) for device_id in device_ids} LOGGER.error('TV configs with these ids: %s were deleted, ' 'while TV configs with these ids: %s were not.', deleted_ids, list(kvstore_keys-set(deleted_ids))) # update current deleted tv subscriptions and # all ipads registered to current user yield process_subscriptions(request_context, async_client_factory, tv_device_ids=device_ids) LOGGER.info('Successful TV Config Delete Request for device_ids=%s', deleted_device_ids)
def process_tv_config_set_request(request_context, client_single_request, single_server_response, async_client_factory): """ This method will process a tvConfigSetRequest. :param request_context: Used to authenticate kvstore requests :param client_single_request: client request object protobuf :param single_server_response: server response object protobuf :param async_client factory: factory class used to generate kvstore and spacebridge clients """ async_kvstore_client = async_client_factory.kvstore_client() tv_config_set_request = client_single_request.tvConfigSetRequest tv_config_proto = tv_config_set_request.tvConfig tv_config = TVConfig() tv_config.from_protobuf(tv_config_proto) timestamp = get_current_timestamp_str() ids_to_update = [tv_config.device_id] # Check to make sure device is valid before proceeding yield validate_devices(set(ids_to_update), request_context, async_kvstore_client) # Before setting config, we need to check if # we're setting a config on a tv that is currently in # a grid configuration, so we need to fetch it. tvs = yield get_drone_mode_tvs(request_context, async_kvstore_client, device_ids=[tv_config.device_id]) if tvs: tv = tvs[0] existing_config = TVConfig(**tv) # If it's a grid, we remove it from the existing grid list, if has_grid(tv, is_json=True) and not has_grid(tv_config_proto): device_ids = ids_to_update = existing_config.tv_grid[constants.DEVICE_IDS] yield remove_from_grid(tv_config=existing_config, device_ids=device_ids, request_context=request_context, async_kvstore_client=async_kvstore_client, timestamp=timestamp) # set timestamp here tv_config.timestamp = timestamp tv_config_json = json.loads(tv_config.to_json()) # delete device id if constants.DEVICE_ID in tv_config_json: del tv_config_json[constants.DEVICE_ID] LOGGER.debug('tv_config_json=%s', tv_config_json) # convert device id to urlsafe b64 encoded to use as kvstore key urlsafe_b64encoded_device_id = b64_to_urlsafe_b64(tv_config.device_id) tv_config_json[constants.KEY] = urlsafe_b64encoded_device_id response = yield async_kvstore_client.async_kvstore_post_or_update_request( constants.DRONE_MODE_TVS_COLLECTION_NAME, json.dumps(tv_config_json), request_context.system_auth_header, key_id=urlsafe_b64encoded_device_id, owner=request_context.current_user) yield check_and_raise_error(response, request_context, 'Write TV Config', valid_codes=[http.OK, http.CREATED]) single_server_response.tvConfigSetResponse.SetInParent() # update tv subscription for config we just created # (and affected grid device ids, if applicable) as well as # all ipads registered to current user yield process_subscriptions(request_context, async_client_factory, tv_device_ids=ids_to_update) LOGGER.info("Successful TV Config Set Request for device id=%s", tv_config.device_id)
def process_tv_config_bulk_set_request(request_context, client_single_request, single_server_response, async_client_factory): """ Bulk tv config set request. Used to bulk send tv config set requests when we're setting a grid configuration :param request_context: Used to authenticate kvstore requests :param client_single_request: client request object protobuf :param single_server_response: server response object protobuf :param async_client factory: factory class used to generate kvstore and spacebridge clients """ async_kvstore_client = async_client_factory.kvstore_client() tv_config_protos = client_single_request.tvConfigBulkSetRequest.tvConfig is_token_update = client_single_request.tvConfigBulkSetRequest.isTokenUpdate LOGGER.debug('tv_config_protos=%s', tv_config_protos) device_ids = {proto.device_id for proto in tv_config_protos} timestamp = get_current_timestamp_str() warnings = yield validate_devices(device_ids, request_context, async_kvstore_client) # Before setting configs, we need to check if # we're setting configs on tvs that are currently in # grid configurations, so we need to fetch them. # This logic is ignored if we're doing a token update existing_grids_to_update = set() if not is_token_update: tvs = yield get_drone_mode_tvs(request_context, async_kvstore_client, device_ids=device_ids) if tvs: requests = [] for tv in tvs: existing_config = TVConfig(**tv) # If it's a grid, we remove it from the existing grid list if has_grid(tv, is_json=True): grid_device_ids = existing_config.tv_grid[constants.DEVICE_IDS] existing_grids_to_update.update(grid_device_ids) request = remove_from_grid(tv_config=existing_config, device_ids=grid_device_ids, request_context=request_context, async_kvstore_client=async_kvstore_client, timestamp=timestamp) requests.append(request) if requests: exceptions = [] responses = yield defer.DeferredList(requests, consumeErrors=True) for response in responses: if isinstance(response[1], Failure): exceptions.append(response[1]) LOGGER.debug('Finished updating modified tvs') if exceptions: LOGGER.error('Encountered exceptions updating tvs, e=%s', exceptions) # if we couldn't update any of the existing configs, we should not continue the request if len(exceptions) == len(requests): raise SpacebridgeApiRequestError('Unable to update all existing tv configs', status_code=http.INTERNAL_SERVER_ERROR) post_data = [] for tv_config_proto in tv_config_protos: tv_config = TVConfig() tv_config.from_protobuf(tv_config_proto) # set timestamp here tv_config.timestamp = timestamp tv_config_json = json.loads(tv_config.to_json()) # delete device id if constants.DEVICE_ID in tv_config_json: del tv_config_json[constants.DEVICE_ID] LOGGER.debug('tv_config_json=%s', tv_config_json) # convert device id to urlsafe b64 encoded to use as kvstore key urlsafe_b64encoded_device_id = b64_to_urlsafe_b64(tv_config.device_id) tv_config_json[constants.KEY] = urlsafe_b64encoded_device_id post_data.append(tv_config_json) LOGGER.debug('post_data=%s', post_data) updated_keys = yield async_kvstore_client.async_batch_save_request( request_context.system_auth_header, constants.DRONE_MODE_TVS_COLLECTION_NAME, post_data, owner=request_context.current_user) ids_to_update = {urlsafe_b64_to_b64(key) for key in updated_keys} ids_to_update.update(existing_grids_to_update) ids_to_update = list(ids_to_update) LOGGER.debug('ids_to_update=%s', ids_to_update) yield process_subscriptions(request_context, async_client_factory, tv_device_ids=ids_to_update) if warnings: single_server_response.tvConfigBulkSetResponse.warnings.extend(warnings) single_server_response.tvConfigBulkSetResponse.SetInParent()