Add load-balanced channel support to RntbdTransportClient (#117)
* WIP (Work In Progress) Merged internal direct implementation to open source branch * Added localhost to test configurations * Safe Deleting collection after test case is done * Trying smaller set of collections * Reverting it back to normal * Merging latest develop branch from internal code base * Merge RNTBD work-in-progress from internal repository * Fixed some request timer issues * Fixed some request timer issues * Fixed some request timer issues * FakeEndpoint is now RntbdRequestTimer-capable * Added a blank line and confirmed that all unit tests pass * Renamed RntbdTransportClient.Options.maxChannels as RntbdTransportClient.Options.maxChannelsPerEndpoint and reduced its default from from 65535 to 10. Impact: Improved reliability and performance. * Renamed RntbdRequestManager.PendingRequest as RntbdRequestRecord and improved error handling in RntbdRequestManager.write to ensure correct retry behavior in the layer above RntbdTransportClient. * RntbdRequestRecord is now a CompletableFuture<StoreResponse>. RntbdServiceEndpoint instances now write RntbdRequestRecord instances. The result: improved encapsulation and code readability. * Code tweaks for correctness * Refactored RntbdTransportClient.Options for improved usability. * Refactored for improved diagnstics, testability, and usability. * Improved request timeout error message * Marked some classes as final * Improved metrics and logger.debug messages. Also: confirmed direct and simple (fast), and unit tests pass locally * Added and updated dependencies (benchmark and direct-impl) on metrics * Corrected merge issues * Improved RntbdMetrics and corrected some deficiencies in RntbdClientChannelPool * Improved RntbdMetrics and corrected some deficiencies in RntbdClientChannelPool * We now report illegal state instead of throwing an illegal state exception in two cases which may be at the root of a failure related to Read/WriteTimeoutExceptions * Corrected an error message * Corrected an error message * Addressed some error recovery/reporting issues * Added RntbdTransportClient specification * Tweaked error handling and removed some dead code * Tweaked error handling and removed some dead code * Tweaked error handling and removed some dead code * Tweaked error handling * Tidied top-level pom.xml
This commit is contained in:
Родитель
b45ca3d066
Коммит
4bd613b809
|
@ -40,7 +40,6 @@
|
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<slf4j.version>1.7.6</slf4j.version>
|
||||
<log4j.version>1.2.17</log4j.version>
|
||||
<metrics.version>3.2.6</metrics.version>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -36,7 +36,8 @@ SOFTWARE.
|
|||
<test.groups>unit</test.groups>
|
||||
<cosmosdb-sdk.version>2.4.5</cosmosdb-sdk.version>
|
||||
<guava.version>27.0.1-jre</guava.version>
|
||||
</properties>
|
||||
<metrics.version>4.0.5</metrics.version>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
<!-- unit test -->
|
||||
|
@ -219,7 +220,6 @@ SOFTWARE.
|
|||
</classpathContainers>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
|
@ -299,6 +299,11 @@ SOFTWARE.
|
|||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
<version>${metrics.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<licenses>
|
||||
<license>
|
||||
|
|
|
@ -0,0 +1,495 @@
|
|||
This specification describes a Netty-based Java implementation for direct-mode access to Azure Cosmos using its proprietary RNTBD protocol. This enables a Java client to create direct channels to Azure Cosmos back-end endpoints.
|
||||
|
||||
RNTBD is intended to be the high-performance TCP transport alternative to HTTP for Cosmos. At its core, the transport stack must implement the `Microsoft.Azure.Documents.TransportClient` contract. It must be able to:
|
||||
|
||||
* Accept requests intended for a Cosmos DB replica.
|
||||
|
||||
* Serialize and send the requests to the intended endpoint ,
|
||||
|
||||
* De-serialize the response and return a StoreResponse in a timely fashion or fail with an appropriate error.
|
||||
|
||||
The `RntbdTransportClient` implements this contract. It extends `TransportClient` and implements its one-and-only-one overridable method:
|
||||
```
|
||||
@Override
|
||||
public Single<StoreResponse> invokeStoreAsync(
|
||||
URI physicalAddress,
|
||||
ResourceOperation operation,
|
||||
RxDocumentServiceRequest request)
|
||||
```
|
||||
|
||||
The `physicalAddress` address in the call to `invokeStoreAsync` is a request URI of the form:
|
||||
|
||||
**rntbd://**_\<host>_**:**_\<port>_**/**_\<replica-path>_
|
||||
|
||||
Netty channel pipelines are created dynamically based on the host and port portion of the URI. The replica path is opaque to the `RntbdTransportClient`.
|
||||
|
||||
Having one connection per remote endpoint might suffice in many cases, but there are reasons to believe the client will benefit from additional connections to the same endpoint in at least two cases:
|
||||
|
||||
* Head-of-line blocking
|
||||
|
||||
Requests or responses can become bottle-necked behind large payloads, as they all must pass sequentially through the same stream.
|
||||
|
||||
* Object contention
|
||||
|
||||
Machines with many cores might attempt to send requests to the same endpoint simultaneously, causing all requests to be sent serially, and then all responses to be dispatched serially.
|
||||
|
||||
For these reasons, the `RntbdTransportClient` supports multiple connections to an endpoint. At least one channel is created for each unique combination of host name and port number. Additional channels are created when the number of pending requests on existing channels gets too high. The set of channels created for a host name and port number are represented as an `RntbdTransportClient.Endpoint`.
|
||||
|
||||
The criteria and thresholds for deciding when to create new channels, when to close existing channels, how to balance traffic among multiple connections, and whether to limit the total number of requests pending to an `RntbdTransportClient.Endpoint` are to be determined. The following section briefly describes the RNTBD protocol and the `RntbdTransportClient` channel pipeline.
|
||||
|
||||
## RNTBD Protocol and the RntbdTransportClient Channel Pipeline
|
||||
|
||||
Following creation of a channel upon receipt of the first `RxDocumentServiceRequest` to an endpoint, the RNTBD protocol requires a two-step context negotiation to complete before message exchange begins.
|
||||
|
||||
1. SSL Handshake
|
||||
|
||||
At the end of this step all future messages will be encrypted. All SSL-related work--negotiation, encryption, and decryption--is performed by Netty's built-in `SSLHandler`. An RNTBD client must only negotiate TLS 1.2 or higher. This is subject to change as security requirements become more stringent over time.
|
||||
|
||||
2. RNTBD context negotiation
|
||||
|
||||
In this step the transport client makes a single request--an `RntbdContextRequest`--and awaits a single response--an `RntbdContext` message.
|
||||
|
||||
Request messages that arrive during context negotiation are queued. After context negotiation is complete all pending requests are sent. Request messages are framed and sent one at a time in arrival order on the channel's outgoing message stream. Response messages may be received in any order. Because of this pending promises of replies are matched with incoming response messages based on Activity ID. The lifetime of an RNTBD channel is illustrated in Figure 1.
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ RNTBD Channel Timeline │
|
||||
└────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ │ │ │
|
||||
│ Client │ │ Service │
|
||||
│ │ │ │
|
||||
└──────────┘ └──────────┘
|
||||
┌──────────┐ ┌──────────┐ ──────────────────────┐
|
||||
│ │ │ │ │
|
||||
│ ├─────────────────┐ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ ┌─────────────┐ └──────────────────▶│ │ │
|
||||
│ │ │SSL Handshake│ │ │ │
|
||||
│ │ └─────────────┘ │ │ Negotiate context │
|
||||
│ │ ┌──────────────────┤ │ │
|
||||
│ │◀─────────────────┘ │ │ │
|
||||
│ │ │ │ │
|
||||
│ ├─RntbdContextRequest────────────────▶│ │ │
|
||||
│ │ │ │ │
|
||||
│ │◀──────────────────────RntbdContext──│ │ │
|
||||
│ │ │ │ ──────────────────────┘
|
||||
│ │ │ │ ──────────────────────┐
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ ├─RntbdRequest[1]────────────────────▶│ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ ├─RntbdRequest[2]────────────────────▶│ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │◀───────────────────RntbdResponse[2]─│ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ ├──RntbdRequest[3]───────────────────▶│ │ │
|
||||
│ │ │ │ Exchange messages │
|
||||
│ │ │ │ │
|
||||
│ │◀───────────────────RntbdResponse[1]─│ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │◀───────────────────RntbdResponse[3]─│ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ . │ │ │
|
||||
│ │ . │ │ │
|
||||
│ │ . │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
└──────────┘ └──────────┘ ──────────────────────┘
|
||||
```
|
||||
*Figure 1. RNTBD Channel Timeline*
|
||||
|
||||
Figure 2 illustrates the `RntbdTransportClient` implementation of the RNTBD protocol. It diagrams the happy-path. Channel management, error handling, and the details of the channel pipeline will be discussed later in this document.
|
||||
<br>
|
||||
|
||||
![Figure 2. RNTBD Channel Implementation Overview ](attachments/RntbdTransportClient.sequence-diagram-1fd9c04f-87e6-472e-afe8-8b1ed30685c2.png)
|
||||
*Figure 2. RNTBD Channel Implementation Overview*
|
||||
|
||||
The next section describes the `RntbdTransportClient` channel implementation.
|
||||
|
||||
## RNTBD Channel Implementation
|
||||
|
||||
Once a connection is established on a channel, the `RntbdTransportClient` begins exchanging messages. Messages written to an RNTBD channel move through a pipeline. This pipeline is the heart of the RNTBD channel implementation.
|
||||
|
||||
There are four outgoing (request) message types and three incoming (response) message types:
|
||||
|
||||
| Request type | Response type | Description
|
||||
| ------------------------- | ----------------| ------------------------------------------------------------------------
|
||||
| `RntbdContextRequest` | `RntbdContext` | Sent on receipt of the first request on a channel. The response indicates that the connected Cosmos Service is either ready or rejects the request to exchange messages.
|
||||
| `RntbdReadRequest` | `RntbdResponse` | A request to read data with an error or non-error response.
|
||||
| `RntbdHealthCheckRequest` | `RntbdHealth` | Sent before each write operation on a channel. A response indicates that the connected Cosmos Service is in good health.
|
||||
| `RntbdWriteRequest` | `RntbdResponse` | A request to write data with an error or non-error response.
|
||||
|
||||
Request and response message types share a common base: `RntbdRequest` and `RntbdResponse`, respectively.
|
||||
|
||||
The RNTBD pipeline is composed of a small number of Netty pipeline handlers:
|
||||
|
||||
| Pipeline class | Role
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------
|
||||
| `SslHandler` | Negotiates a secure connection. Subsequently encrypts data written to the channel's output stream and decrypts data read from the channel's input stream. The `SslHandler` reads/writes bytes. It knows nothing about RNTBD messages.
|
||||
| `RntbdClientMessageFormatter` | Encodes `RntbdRequest` messages (to bytes) and decodes `RntbdResponse` messages (from bytes).
|
||||
| `RntbdContextNegotiator` | Injects an `RntbdContextRequest` message upon receipt of the first `RntbdRequest` message on a channel. `RntbdRequest` messages are queued and left pending until receipt of an `RntbdContext` message. If the response acknowledges that the connected Cosmos service is ready to exchange messages, all pending messages are sent down the pipeline. On rejection, failure, cancellation, or timeout, the channel is closed and all pending responses are completed exceptionally.
|
||||
| `RntbdHealthChecker` | Injects an `RntbdHealthCheckRequest` message upon receipt of an `RntbdWriteRequest` message. The `RntbdWriteRequest` is left pending until an associated `RntbdHealth` response is received. The `RntbdWriteRequest` message is then sent down the pipeline. On failure, cancellation, or timeout, the `RntbdWriteRequest` is completed exceptionally with an indication that the `RntbdWriteRequest` is retry-able.
|
||||
| `RntbdClientMessageManager` | Creates and completes `CompletableFuture<StoreResponse>` instances. Handles cancellations, exceptions, and timeouts. Maintains the `RntbdContext`.
|
||||
|
||||
## Health Checks, Write Operations, and Channel Failures
|
||||
|
||||
Cosmos writes are not idempotent at the server. For this reason, the `RntbdTransportClient` must avoid sending writes through failed channels to whatever extent possible. This is the reason for sending write requests in two stages:
|
||||
|
||||
* Check that the channel is healthy and then--only if the server reports that the connection is healthy--
|
||||
|
||||
* Proceed with the write operation
|
||||
|
||||
The sequence of health check followed by write operation introduces a [time-of-check to time-of-use (TOCTOU)](https://en.wikipedia.org/wiki/Time_of_check_to_time_of_use) race condition. Even if the health check succeeds, the write operation could fail any time before the client receives a response--before sending the payload, while sending, or while waiting for a response.
|
||||
|
||||
There is no way to close the race. The race window should be as small as possible. For this reason success responses to health checks aren't cached. That said, it’s not possible to reduce the probability of write failures to zero. When a failure is detected (on read, write, or health check operation), the channel will be closed and the `CompletableFuture<StoreResponse>` associated with each pending request will be completed exceptionally. The error associated with a failed `CompletableFuture<StoreResponse>` will indicate whether a failed request is retry-able.
|
||||
|
||||
## Cancellations, Exceptions, and Timeouts
|
||||
|
||||
Cancellations may be initiated by `RntbdTransportClient` consumers using the `Single<StoreResponse>` returned by `RntbdTransportClient.invokeStoreAsync`.
|
||||
|
||||
Four types of exceptions are thrown by elements of the RNTBD pipeline:
|
||||
|
||||
* IllegalArgumentException is thrown when the argument to a function doesn't conform to the contract for a method and is indicative of a bug in the RNTBD channel pipeline code.
|
||||
|
||||
* IllegalStateException is thrown when the state of a pipeline, request, or response object doesn't conform to expectations. It is indicative of a bug in the RNTBD channel pipeline code or an object passed to it.
|
||||
|
||||
* CorruptedFrameException is thrown when an IO error is detected. This means there was a problem reading/writing bytes to the wire. It is indicative of a channel health issue.
|
||||
|
||||
* SocketException (or one of its derivatives) are raised when a connection cannot be established or an established connection drops or hangs.
|
||||
|
||||
## Exception propagation
|
||||
|
||||
Exception propagate through the `RntbdTransportClient` like this:
|
||||
|
||||
* Connection exceptions associated with opening a channel propagate to the future listener added by RntbdTransportClient.Endpoint.write.
|
||||
|
||||
* Outbound channel exceptions propagate to the future listener added by RntbdTransportClient.Endpoint.doWrite
|
||||
|
||||
* Inbound channel exceptions are caught by RntbdRequestManager.exceptionCaught and propagate to the RxJava Single emitter returned by RntbdTransportClient.invokeStoreAsync.
|
||||
|
||||
#### Exception mapping
|
||||
|
||||
TODO: DANOBLE
|
||||
|
||||
## Performance Requirements
|
||||
|
||||
The performance requirements of the transport client stack are somewhat fuzzy. As it’s one of the core components of the system, the per-request overhead must be minimal. The overhead is best emphasized by tiny reads (<1 KiB) at eventual consistency, in the same region (1 ms RTT).
|
||||
|
||||
Each connection should be able to handle around 2,500-5,000 requests per second. Given enough cores and memory, the transport stack must be able to handle around 30,000-60,000 requests per second, per endpoint.
|
||||
|
||||
The current version of the protocol performs worse than necessary because all headers are in a flat structure. As such, the (inherently single-threaded) logic that decodes a request header to determine which pending I/O to complete must parse more data than necessary, lowering the peak attainable throughput per connection. Fixing that requires a protocol breaking change (incrementing the protocol version number and rolling out in a deliberate fashion).
|
||||
|
||||
## Confidentiality & Integrity
|
||||
|
||||
The client must only negotiate TLS 1.2 or newer. This is subject to change, as security requirements become more stringent over time.
|
||||
|
||||
## Pass-through Tokens
|
||||
|
||||
RNTBD requests carry a correlation ID which is called `activityID` throughout the `RntbdTransportClient` implementation. An `activityID` is propagated to remote endpoints on every request.
|
||||
The same holds for the user agent string which is included in the `RntbdContext` associated with a channel.
|
||||
|
||||
## RNTBD Message Formats
|
||||
|
||||
Each RNTBD message type consists of two parts:
|
||||
|
||||
* a head frame followed by
|
||||
* an optional body frame.
|
||||
|
||||
Each frame begins with a 32-bit integer `length` field followed by a payload.
|
||||
|
||||
```
|
||||
┌─────────────┬──────────────────┐
|
||||
│ length (32) │ payload (0...) ...
|
||||
└─────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
The definition of the `length` field is different for the head and body frames.
|
||||
|
||||
| Frame | Definition of `length` field
|
||||
| ----- | --------------------------------------------------------------------------------------------------------------
|
||||
| head | Length of the frame (four plus the length of the payload) expressed as an unsigned 32-bit integer in little-endian order. Values greater than `Integer.MAX_VALUE` must not be sent.
|
||||
| body | Length of the payload expressed as a 32-bit integer in little-endian order. Values greater than `Integer.MAX_VALUE` must not be sent.
|
||||
|
||||
The `RntbdTransportClient` sends request messages and receives response messages. Following RNTBD context negotiation there is no guarantee that responses will be received in the order that requests are sent. Hence, it is the job of the `RntbdTransportClient` to match requests to responses based on *Activity ID*.
|
||||
|
||||
## RNTBD Request Messages
|
||||
|
||||
RNTBD request messages have this format:
|
||||
|
||||
```
|
||||
RntbdRequestHead frame
|
||||
┌────────────────────────────────────┬──────────────────┬──────────────────┬────────────────────────────────────────────
|
||||
│length (32) │resourceType (16) │operationType (16)│activityID (128)
|
||||
└────────────────────────────────────┴──────────────────┴──────────────────┴────────────────────────┬──────────────────┐
|
||||
│headers (0...) ...
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────────┘
|
||||
|
||||
RntbdRequestBody frame
|
||||
┌────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│length (32) │ payload (0...) ...
|
||||
└────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The fields of the `RntbdRequestHead` are defined as:
|
||||
|
||||
| Field | Definition
|
||||
| ------------- | -----------------------------------------------------------------------------------------------------
|
||||
| length | Length of the frame expressed as an unsigned 32-bit integer in little-endian order. Values greater than `Integer.MAX_VALUE` must not be sent.
|
||||
| resourceType | A 16-bit `RntbdResourceType.id()` value in little-endian order.
|
||||
| operationType | A 16-bit `RntbdOperationType.id()` value in little-endian order.
|
||||
| activityId | A 128-bit Activity ID that uniquely identifies the request message. It is a `UUID` serialized as an `int` followed by three `short` and six `byte` values in little-endian order. This serialization yields the same sequence of bytes as produced by `System.Guid.ToByteArray`.
|
||||
| headers | A variable-length sequence of RNTBD headers expressed as `RntbdToken` values.
|
||||
|
||||
A description of each message type follows.
|
||||
|
||||
### RntbdContextRequest
|
||||
|
||||
An `RntbdContextRequest` consists of a single frame: an `RntbdRequestHead` with three headers.
|
||||
|
||||
```
|
||||
┌──────────────────────────────┬──────────────┬──────────────┬─────────────────────────────────────────────────────────┬
|
||||
│length (32) │0x0000 (16) │0x0000 (16) │activityID (128) ...
|
||||
└──────────────────────────────┴──────────────┴──────────────┴─────────────────────────────────────────────────────────┴
|
||||
┬─────────────────────────────────────────────┬ ┬───────────────────────────────────────────────────────┐
|
||||
│protocolVersion: RntbdTokenType.ULong (56) ... │clientVersion: RntbdTokenType.SmallString (32..2072) ...
|
||||
┴─────────────────────────────────────────────┴ ┴───────────────────────────────────────────────────────┘
|
||||
┬───────────────────────────────────────────────────────────┐
|
||||
│userAgent: RntbdTokenType.SmallString (32..2072) ...
|
||||
┴───────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The fields of the `RntbdContextRequest` are defined as follows:
|
||||
|
||||
| Field | Definition
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------
|
||||
| length | Length of the frame expressed as an unsigned 32-bit integer in little-endian order.
|
||||
| resourceType | A 16-bit value of zero. This is the value of `RntbdResourceType.Connection.id()`.
|
||||
| operationType | A 16-bit value of zero. This is the value of `RntbdOperationType.Connection.id()`.
|
||||
| protocolVersion | The protocol version to be negotiated. It is represented as a 56-bit long `RntbdToken` of type `RntbdTokenType.ULong`. Its token identifier is `RntbdContextRequestHeader.ProtocolVersion.id()`.
|
||||
| clientVersion | The client version string. It is represented as a variable-length `RntbdToken` of type `RntbdTokenType.ShortString`. Its token identifier is `RntbdContextRequestHeader.ClientVersion.id()`.
|
||||
| userAgent | A string identifying the user agent. It is represented as a variable-length `RntbdToken` of type `RntbdTokenType.ShortString`. Its token identifier is `RntbdContextRequestHeader.UserAgent.id()`.
|
||||
|
||||
|
||||
### RntbdHealthCheckRequest
|
||||
|
||||
TODO: DANOBLE
|
||||
|
||||
### RntbdReadRequest
|
||||
|
||||
TODO: DANOBLE
|
||||
|
||||
### RntbdWriteRequest
|
||||
|
||||
TODO: DANOBLE
|
||||
|
||||
## RNTBD Response Messages
|
||||
|
||||
RNTBD response messages have this format:
|
||||
|
||||
```
|
||||
RntbdResponseHead frame
|
||||
┌────────────────────────────────────┬──────────────────┬───────────────────────────────────────────────────────────────
|
||||
│length (32) │status (16) │activityID (128)
|
||||
└────────────────────────────────────┴──────────────────┴────────────────────────┬─────────────────────────────────────┐
|
||||
│headers (0...) ...
|
||||
─────────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────┘
|
||||
|
||||
RntbdResponseBody frame
|
||||
┌────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│length (32) │ payload (0...) ...
|
||||
└────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
The fields of the `RntbdResponseHead` are defined as:
|
||||
|
||||
| Field | Definition
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------
|
||||
| length | Length of the frame expressed as an unsigned 32-bit integer in little-endian order. Values greater than `Integer.MAX_VALUE` must not be sent.
|
||||
| status | A 32-bit `HttpResponseStatus` code in little-endian order.
|
||||
| activityId | A 128-bit Activity ID that uniquely identifies the request message. It is a `UUID` serialized as an `int` followed by three `short` and six `byte` values in little-endian order. This serialization yields the same sequence of bytes as produced by `System.Guid.ToByteArray`.
|
||||
| headers | A variable-length sequence of RNTBD headers expressed as `RntbdToken` values.
|
||||
|
||||
|
||||
## RNTBD Headers
|
||||
|
||||
RNTBD headers are expressed as a sequence of `RntbdToken` instances with this wire format:
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────┐
|
||||
│id (16) │type (8)│value (0...) ...
|
||||
└────────────────┴────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
The fields of an `RntbdToken` instance are defined as:
|
||||
|
||||
| Field | Definition
|
||||
| ------ | ------------------------------------------------------------------------------------------------------
|
||||
| id | A 16-bit `RntbdRequestHeader` or `RntbdResponseHeader` identifier in little-endian order.
|
||||
| type | An 8-bit `RntbdTokenType` ID.
|
||||
| value | Value of the header identified by `id`. The format and length of the value depend on the `type`.
|
||||
|
||||
Here are the formats of `RntbdToken` instance values by `RntbdTokenType`.
|
||||
|
||||
### RntbdTokenType.Byte (0x00)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.UShort` represents an unsigned 8-bit integer-valued header. It has a fixed
|
||||
length of 4 bytes (32 bits).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────┐
|
||||
│id (16) │0x00 (8)│val (8) │
|
||||
└────────────────┴────────┴────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.UShort (0x01)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.UShort` represents an unsigned 16-bit integer-valued header. It has a fixed
|
||||
length of 5 bytes (40 bits).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────┐
|
||||
│id (16) │0x01 (8)│ value (16) │
|
||||
└────────────────┴────────┴────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.ULong (0x02)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.ULong` represents an unsigned 32-bit integer-valued header. It has a fixed
|
||||
length of 7 bytes (56 bits).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────┐
|
||||
│id (16) │0x02 (8)│value (32) │
|
||||
└────────────────┴────────┴────────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.Long (0x03)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.Long` represents an signed 32-bit integer-valued header. It has a fixed length of 7 bytes (56 bits).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────┐
|
||||
│id (16) │0x03 (8)│value (32) │
|
||||
└────────────────┴────────┴────────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.ULongLong (0x04)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.ULongLong` represents an unsigned 64-bit integer-valued header. It has a fixed length of 11 bytes (88 bits).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────────────────────────────────────┐
|
||||
│id (16) │0x04 (8)│value (64) │
|
||||
└────────────────┴────────┴────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.LongLong (0x05)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.LongLong` represents a signed 64-bit integer-valued header. It has a fixed
|
||||
length of 11 bytes (88 bits).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────────────────────────────────────┐
|
||||
│id (16) │ 0x05 │ (64) │
|
||||
└────────────────┴────────┴────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.Guid (0x06)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.Guid` represents a 128-bit UUID serializes using an algorithm that produces
|
||||
the same byte sequence as [`System.Guid.ToByteArray`](https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray?view=netframework-4.7.2). It has a fixed length of 19 bytes (152 bits).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬─────────────────────────────────────────────────────────────────────────────────────────────
|
||||
│id (16) │0x06 (8)│ value (128)
|
||||
└────────────────┴────────┴─────────────────────────────────────────────────────────────────────────────────────────────
|
||||
───────────────────────────────────┐
|
||||
│
|
||||
───────────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.SmallString (0x07)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.SmallString` represents a UTF-8 encoded string-valued header. It has a variable length between 4 bytes (32 bits) and 259 bytes (2,072 bits). Its encoded string value can be as large as 255 bytes.
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────┬────────────────────────────┐
|
||||
│id (16) │0x07 (8)│len (8) │ val (0..0xFF) ...
|
||||
└────────────────┴────────┴────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.String (0x08)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.String` represents a UTF-8 encoded string-valued header. It has a variable length between 5 bytes (40 bits) and 2,147,483,654 bytes (524,320 bits). Its encoded string value can be as large as
|
||||
65,535 bytes (64 KiB - 1 byte).
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────┬────────────────────────────┐
|
||||
│id (16) │0x08 (8)│len (16) │ val (0..0xFFFF) ...
|
||||
└────────────────┴────────┴────────────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.ULongString (0x09)
|
||||
|
||||
An `RntbdToken` of type `RntbdTokenType.ULongString` represents a UTF-8 encoded string-valued header. It has a variable length between 7 bytes (56 bits) and 2,147,483,654 bytes (a little more than 16 gibits). Its encoded string value can be as large as 2,147,483,647 (2**31 - 1) bytes.
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────┬────────────────────────────┐
|
||||
│id (16) │0x09 (8)│len (32) │ val (0..0x7FFFFFFF) ...
|
||||
└────────────────┴────────┴────────────────────────────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.SmallBytes (0x0A)
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────┬────────────────────────────┐
|
||||
│id (16) │ 0x0A │len (8) │ val (0..0xFF) ...
|
||||
└────────────────┴────────┴────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.Bytes (0x0B)
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────┬────────────────────────────┐
|
||||
│id (16) │ 0x0B │len (16) │ val (0..0xFFFF) ...
|
||||
└────────────────┴────────┴────────────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.ULongBytes (0x0C)
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────┬────────────────────────────┐
|
||||
│id (16) │ 0x0C │len (32) │ val (0..0x7FFFFFFF) ...
|
||||
└────────────────┴────────┴────────────────────────────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.Float (0x0D)
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────┐
|
||||
│id (16) │ 0x0D │ val (32) │
|
||||
└────────────────┴────────┴────────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.Double (0x0E)
|
||||
|
||||
```
|
||||
┌────────────────┬────────┬────────────────────────────────────────────────────────────────┐
|
||||
│id (16) │ 0x0E │ val (64) │
|
||||
└────────────────┴────────┴────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### RntbdTokenType.Invalid (0xFF)
|
||||
|
||||
```
|
||||
┌────────────────┬────────┐
|
||||
│id (16) │ 0x0F │
|
||||
└────────────────┴────────┘
|
||||
```
|
Двоичные данные
direct-impl/specifications/RntbdTransportClient/RntbdTransportClient.pptx
Normal file
Двоичные данные
direct-impl/specifications/RntbdTransportClient/RntbdTransportClient.pptx
Normal file
Двоичный файл не отображается.
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 103 KiB |
Двоичные данные
direct-impl/specifications/RntbdTransportClient/src/RntbdTransportClient architecture.vsdx
Normal file
Двоичные данные
direct-impl/specifications/RntbdTransportClient/src/RntbdTransportClient architecture.vsdx
Normal file
Двоичный файл не отображается.
Двоичные данные
direct-impl/specifications/RntbdTransportClient/src/RntbdTransportClient sequence diagram.vsdx
Normal file
Двоичные данные
direct-impl/specifications/RntbdTransportClient/src/RntbdTransportClient sequence diagram.vsdx
Normal file
Двоичный файл не отображается.
|
@ -24,66 +24,68 @@
|
|||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.microsoft.azure.cosmosdb.DocumentClientException;
|
||||
import com.microsoft.azure.cosmosdb.internal.UserAgentContainer;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdClientChannelInitializer;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdEndpoint;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdMetrics;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdObjectMapper;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestArgs;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestManager;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdServiceEndpoint;
|
||||
import com.microsoft.azure.cosmosdb.rx.internal.Configs;
|
||||
import com.microsoft.azure.cosmosdb.rx.internal.RxDocumentServiceRequest;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import rx.Single;
|
||||
import rx.SingleEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static com.microsoft.azure.cosmosdb.internal.HttpConstants.HttpHeaders;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdReporter.reportIssueUnless;
|
||||
|
||||
final public class RntbdTransportClient extends TransportClient implements AutoCloseable {
|
||||
@JsonSerialize(using = RntbdTransportClient.JsonSerializer.class)
|
||||
public final class RntbdTransportClient extends TransportClient implements AutoCloseable {
|
||||
|
||||
// region Fields
|
||||
|
||||
final private static String className = RntbdTransportClient.class.getName();
|
||||
final private static AtomicLong counter = new AtomicLong(0L);
|
||||
final private static Logger logger = LoggerFactory.getLogger(className);
|
||||
private static final AtomicLong instanceCount = new AtomicLong();
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdTransportClient.class);
|
||||
private static final String namePrefix = RntbdTransportClient.class.getSimpleName() + '-';
|
||||
|
||||
final private AtomicBoolean closed = new AtomicBoolean(false);
|
||||
final private EndpointFactory endpointFactory;
|
||||
final private String name;
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
private final RntbdEndpoint.Provider endpointProvider;
|
||||
private final RntbdMetrics metrics;
|
||||
private final String name;
|
||||
|
||||
// endregion
|
||||
|
||||
// region Constructors
|
||||
|
||||
RntbdTransportClient(EndpointFactory endpointFactory) {
|
||||
this.name = className + '-' + counter.incrementAndGet();
|
||||
this.endpointFactory = endpointFactory;
|
||||
RntbdTransportClient(final RntbdEndpoint.Provider endpointProvider) {
|
||||
this.name = RntbdTransportClient.namePrefix + RntbdTransportClient.instanceCount.incrementAndGet();
|
||||
this.endpointProvider = endpointProvider;
|
||||
this.metrics = new RntbdMetrics(this.name);
|
||||
}
|
||||
|
||||
RntbdTransportClient(Options options, SslContext sslContext, UserAgentContainer userAgent) {
|
||||
this(new EndpointFactory(options, sslContext, userAgent));
|
||||
RntbdTransportClient(final Options options, final SslContext sslContext) {
|
||||
this(new RntbdServiceEndpoint.Provider(options, sslContext));
|
||||
}
|
||||
|
||||
RntbdTransportClient(Configs configs, int requestTimeoutInSeconds, UserAgentContainer userAgent) {
|
||||
this(new Options(Duration.ofSeconds((long)requestTimeoutInSeconds)), configs.getSslContext(), userAgent);
|
||||
RntbdTransportClient(final Configs configs, final int requestTimeoutInSeconds, final UserAgentContainer userAgent) {
|
||||
this(new Options.Builder(requestTimeoutInSeconds).userAgent(userAgent).build(), configs.getSslContext());
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
@ -93,382 +95,164 @@ final public class RntbdTransportClient extends TransportClient implements AutoC
|
|||
@Override
|
||||
public void close() {
|
||||
|
||||
logger.debug("\n [{}] CLOSE", this);
|
||||
|
||||
if (this.closed.compareAndSet(false, true)) {
|
||||
|
||||
this.endpointFactory.close().addListener(future -> {
|
||||
|
||||
if (future.isSuccess()) {
|
||||
|
||||
// TODO: DANOBLE: Deal with fact that all channels are closed, but each of their sockets are open
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/367028
|
||||
// Notes:
|
||||
// Observation: Closing/shutting down a channel does not cause its underlying socket to be closed
|
||||
// Option: Pool SocketChannel instances and manage the SocketChannel used by each NioSocketChannel
|
||||
// Option: Inherit from NioSocketChannel to ensure the behavior we'd like (close means close)
|
||||
// Option: Recommend that customers increase their system's file descriptor count (e.g., on macOS)
|
||||
|
||||
logger.info("{} closed", this);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error("{} close failed: {}", this, future.cause());
|
||||
});
|
||||
|
||||
} else {
|
||||
logger.debug("{} already closed", this);
|
||||
this.endpointProvider.close();
|
||||
this.metrics.close();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("\n [{}]\n already closed", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<StoreResponse> invokeStoreAsync(
|
||||
URI physicalAddress, ResourceOperation unused, RxDocumentServiceRequest request
|
||||
final URI physicalAddress, final ResourceOperation unused, final RxDocumentServiceRequest request
|
||||
) {
|
||||
Objects.requireNonNull(physicalAddress, "physicalAddress");
|
||||
Objects.requireNonNull(request, "request");
|
||||
checkNotNull(physicalAddress, "physicalAddress");
|
||||
checkNotNull(request, "request");
|
||||
this.throwIfClosed();
|
||||
|
||||
final RntbdRequestArgs requestArgs = new RntbdRequestArgs(request, physicalAddress.getPath());
|
||||
final Endpoint endpoint = this.endpointFactory.getEndpoint(physicalAddress);
|
||||
final RntbdRequestArgs requestArgs = new RntbdRequestArgs(request, physicalAddress);
|
||||
|
||||
final CompletableFuture<StoreResponse> responseFuture = endpoint.write(requestArgs);
|
||||
if (logger.isDebugEnabled()) {
|
||||
requestArgs.traceOperation(logger, null, "invokeStoreAsync");
|
||||
logger.debug("\n [{}]\n {}\n INVOKE_STORE_ASYNC", this, requestArgs);
|
||||
}
|
||||
|
||||
return Single.fromEmitter(emitter -> responseFuture.whenComplete((response, error) -> {
|
||||
final RntbdEndpoint endpoint = this.endpointProvider.get(physicalAddress);
|
||||
this.metrics.incrementRequestCount();
|
||||
|
||||
requestArgs.traceOperation(logger, null, "emitSingle", response, error);
|
||||
final CompletableFuture<StoreResponse> future = endpoint.request(requestArgs);
|
||||
|
||||
if (error == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{} [physicalAddress: {}, activityId: {}] Request succeeded with response status: {}",
|
||||
endpoint, physicalAddress, request.getActivityId(), response.getStatus()
|
||||
);
|
||||
return Single.fromEmitter((SingleEmitter<StoreResponse> emitter) -> {
|
||||
|
||||
future.whenComplete((response, error) -> {
|
||||
|
||||
requestArgs.traceOperation(logger, null, "emitSingle", response, error);
|
||||
this.metrics.incrementResponseCount();
|
||||
|
||||
if (error == null) {
|
||||
emitter.onSuccess(response);
|
||||
} else {
|
||||
if (!(error instanceof DocumentClientException)) {
|
||||
logger.warn("{} expected failure of {}, not ", requestArgs, DocumentClientException.class, error);
|
||||
}
|
||||
this.metrics.incrementErrorResponseCount();
|
||||
emitter.onError(error instanceof CompletionException ? error.getCause() : error);
|
||||
}
|
||||
emitter.onSuccess(response);
|
||||
|
||||
} else {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("{} [physicalAddress: {}, activityId: {}] Request failed: {}",
|
||||
endpoint, physicalAddress, request.getActivityId(), error.getMessage()
|
||||
);
|
||||
}
|
||||
emitter.onError(error);
|
||||
}
|
||||
|
||||
requestArgs.traceOperation(logger, null, "completeEmitSingle");
|
||||
}));
|
||||
requestArgs.traceOperation(logger, null, "emitSingleComplete");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return '[' + name + ", endpointCount: " + this.endpointFactory.endpoints.mappingCount() + ']';
|
||||
return RntbdObjectMapper.toJson(this);
|
||||
}
|
||||
|
||||
private void throwIfClosed() {
|
||||
if (this.closed.get()) {
|
||||
throw new IllegalStateException(String.format("%s is closed", this));
|
||||
}
|
||||
checkState(!this.closed.get(), "%s is closed", this);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Types
|
||||
|
||||
interface Endpoint {
|
||||
static final class JsonSerializer extends StdSerializer<RntbdTransportClient> {
|
||||
|
||||
Future<?> close();
|
||||
|
||||
CompletableFuture<StoreResponse> write(RntbdRequestArgs requestArgs);
|
||||
}
|
||||
|
||||
private static class DefaultEndpoint implements Endpoint {
|
||||
|
||||
final private ChannelFuture channelFuture;
|
||||
final private RntbdRequestManager requestManager;
|
||||
|
||||
DefaultEndpoint(EndpointFactory factory, URI physicalAddress) {
|
||||
|
||||
final RntbdClientChannelInitializer clientChannelInitializer = factory.createClientChannelInitializer();
|
||||
this.requestManager = clientChannelInitializer.getRequestManager();
|
||||
final int connectionTimeout = factory.getConnectionTimeout();
|
||||
|
||||
final Bootstrap bootstrap = new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(factory.eventLoopGroup)
|
||||
.handler(clientChannelInitializer)
|
||||
.option(ChannelOption.AUTO_READ, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
|
||||
.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
|
||||
this.channelFuture = bootstrap.connect(physicalAddress.getHost(), physicalAddress.getPort());
|
||||
public JsonSerializer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public Future<?> close() {
|
||||
return this.channelFuture.channel().close();
|
||||
public JsonSerializer(Class<RntbdTransportClient> type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.channelFuture.channel().toString();
|
||||
}
|
||||
public void serialize(RntbdTransportClient value, JsonGenerator generator, SerializerProvider provider) throws IOException {
|
||||
|
||||
public CompletableFuture<StoreResponse> write(RntbdRequestArgs requestArgs) {
|
||||
generator.writeStartObject();
|
||||
|
||||
Objects.requireNonNull(requestArgs, "requestArgs");
|
||||
generator.writeArrayFieldStart(value.name);
|
||||
|
||||
final CompletableFuture<StoreResponse> responseFuture = this.requestManager.createStoreResponseFuture(requestArgs);
|
||||
|
||||
this.channelFuture.addListener((ChannelFuture future) -> {
|
||||
|
||||
if (future.isSuccess()) {
|
||||
requestArgs.traceOperation(logger, null, "doWrite");
|
||||
logger.debug("{} connected", future.channel());
|
||||
doWrite(future.channel(), requestArgs);
|
||||
return;
|
||||
}
|
||||
|
||||
UUID activityId = requestArgs.getActivityId();
|
||||
|
||||
if (future.isCancelled()) {
|
||||
|
||||
this.requestManager.cancelStoreResponseFuture(activityId);
|
||||
|
||||
logger.debug("{}{} request cancelled: ", future.channel(), requestArgs, future.cause());
|
||||
|
||||
} else {
|
||||
|
||||
final Channel channel = future.channel();
|
||||
Throwable cause = future.cause();
|
||||
|
||||
logger.error("{}{} request failed: ", channel, requestArgs, cause);
|
||||
|
||||
GoneException goneException = new GoneException(
|
||||
String.format("failed to establish connection to %s: %s",
|
||||
channel.remoteAddress(), cause.getMessage()
|
||||
),
|
||||
cause instanceof Exception ? (Exception)cause : new IOException(cause.getMessage(), cause),
|
||||
ImmutableMap.of(HttpHeaders.ACTIVITY_ID, activityId.toString()),
|
||||
requestArgs.getReplicaPath()
|
||||
);
|
||||
|
||||
logger.debug("{}{} {} mapped to GoneException: ",
|
||||
channel, requestArgs, cause.getClass(), goneException
|
||||
);
|
||||
|
||||
this.requestManager.completeStoreResponseFutureExceptionally(activityId, goneException);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return responseFuture;
|
||||
}
|
||||
|
||||
private static void doWrite(Channel channel, RntbdRequestArgs requestArgs) {
|
||||
|
||||
channel.write(requestArgs).addListener((ChannelFuture future) -> {
|
||||
|
||||
requestArgs.traceOperation(logger, null, "writeComplete", future.channel());
|
||||
|
||||
if (future.isSuccess()) {
|
||||
|
||||
logger.debug("{} request sent: {}", future.channel(), requestArgs);
|
||||
|
||||
} else if (future.isCancelled()) {
|
||||
|
||||
logger.debug("{}{} request cancelled: {}",
|
||||
future.channel(), requestArgs, future.cause().getMessage()
|
||||
);
|
||||
|
||||
} else {
|
||||
Throwable cause = future.cause();
|
||||
logger.error("{}{} request failed due to {}: {}",
|
||||
future.channel(), requestArgs, cause.getClass(), cause.getMessage()
|
||||
);
|
||||
value.endpointProvider.list().forEach(endpoint -> {
|
||||
try {
|
||||
generator.writeObject(endpoint);
|
||||
} catch (IOException error) {
|
||||
logger.error("failed to serialize {} due to ", endpoint.getName(), error);
|
||||
}
|
||||
});
|
||||
|
||||
generator.writeEndArray();
|
||||
|
||||
generator.writeObjectField("config", value.endpointProvider.config());
|
||||
generator.writeObjectField("metrics", value.metrics);
|
||||
generator.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
static class EndpointFactory {
|
||||
|
||||
final private ConcurrentHashMap<String, Endpoint> endpoints = new ConcurrentHashMap<>();
|
||||
final private NioEventLoopGroup eventLoopGroup;
|
||||
final private Options options;
|
||||
final private SslContext sslContext;
|
||||
final private UserAgentContainer userAgent;
|
||||
|
||||
EndpointFactory(Options options, SslContext sslContext, UserAgentContainer userAgent) {
|
||||
|
||||
Objects.requireNonNull(options, "options");
|
||||
Objects.requireNonNull(sslContext, "sslContext");
|
||||
Objects.requireNonNull(userAgent, "userAgent");
|
||||
|
||||
final DefaultThreadFactory threadFactory = new DefaultThreadFactory("CosmosEventLoop", true);
|
||||
final int threadCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
this.eventLoopGroup = new NioEventLoopGroup(threadCount, threadFactory);
|
||||
this.options = options;
|
||||
this.sslContext = sslContext;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
int getConnectionTimeout() {
|
||||
return (int)this.options.getOpenTimeout().toMillis();
|
||||
}
|
||||
|
||||
Options getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
UserAgentContainer getUserAgent() {
|
||||
return this.userAgent;
|
||||
}
|
||||
|
||||
Future<?> close() {
|
||||
return this.eventLoopGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
RntbdClientChannelInitializer createClientChannelInitializer() {
|
||||
|
||||
final LogLevel logLevel;
|
||||
|
||||
if (RntbdTransportClient.logger.isTraceEnabled()) {
|
||||
logLevel = LogLevel.TRACE;
|
||||
} else if (RntbdTransportClient.logger.isDebugEnabled()) {
|
||||
logLevel = LogLevel.DEBUG;
|
||||
} else {
|
||||
logLevel = null;
|
||||
}
|
||||
|
||||
return new RntbdClientChannelInitializer(this.userAgent, this.sslContext, logLevel, this.options);
|
||||
}
|
||||
|
||||
Endpoint createEndpoint(URI physicalAddress) {
|
||||
return new DefaultEndpoint(this, physicalAddress);
|
||||
}
|
||||
|
||||
void deleteEndpoint(URI physicalAddress) {
|
||||
|
||||
// TODO: DANOBLE: Utilize this method of tearing down unhealthy endpoints
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/331552
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/331593
|
||||
|
||||
final String authority = physicalAddress.getAuthority();
|
||||
final Endpoint endpoint = this.endpoints.remove(authority);
|
||||
|
||||
if (endpoint == null) {
|
||||
throw new IllegalArgumentException(String.format("physicalAddress: %s", physicalAddress));
|
||||
}
|
||||
|
||||
endpoint.close().addListener(future -> {
|
||||
|
||||
if (future.isSuccess()) {
|
||||
logger.info("{} closed channel of communication with {}", endpoint, authority);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error("{} failed to close channel of communication with {}: {}", endpoint, authority, future.cause());
|
||||
});
|
||||
}
|
||||
|
||||
Endpoint getEndpoint(URI physicalAddress) {
|
||||
return this.endpoints.computeIfAbsent(
|
||||
physicalAddress.getAuthority(), authority -> this.createEndpoint(physicalAddress)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final public static class Options {
|
||||
public static final class Options {
|
||||
|
||||
// region Fields
|
||||
|
||||
private String certificateHostNameOverride;
|
||||
private int maxChannels;
|
||||
private int maxRequestsPerChannel;
|
||||
private Duration openTimeout = Duration.ZERO;
|
||||
private int partitionCount;
|
||||
private Duration receiveHangDetectionTime;
|
||||
private Duration requestTimeout;
|
||||
private Duration sendHangDetectionTime;
|
||||
private Duration timerPoolResolution = Duration.ZERO;
|
||||
private UserAgentContainer userAgent = null;
|
||||
private final String certificateHostNameOverride;
|
||||
private final int maxChannelsPerEndpoint;
|
||||
private final int maxRequestsPerChannel;
|
||||
private final Duration connectionTimeout;
|
||||
private final int partitionCount;
|
||||
private final Duration receiveHangDetectionTime;
|
||||
private final Duration requestTimeout;
|
||||
private final Duration sendHangDetectionTime;
|
||||
private final UserAgentContainer userAgent;
|
||||
|
||||
// endregion
|
||||
|
||||
// region Constructors
|
||||
|
||||
public Options(int requestTimeoutInSeconds) {
|
||||
this(Duration.ofSeconds((long)requestTimeoutInSeconds));
|
||||
}
|
||||
private Options(Builder builder) {
|
||||
|
||||
public Options(Duration requestTimeout) {
|
||||
|
||||
Objects.requireNonNull(requestTimeout);
|
||||
|
||||
if (requestTimeout.compareTo(Duration.ZERO) <= 0) {
|
||||
throw new IllegalArgumentException("requestTimeout");
|
||||
}
|
||||
|
||||
this.maxChannels = 0xFFFF;
|
||||
this.maxRequestsPerChannel = 30;
|
||||
this.partitionCount = 1;
|
||||
this.receiveHangDetectionTime = Duration.ofSeconds(65L);
|
||||
this.requestTimeout = requestTimeout;
|
||||
this.sendHangDetectionTime = Duration.ofSeconds(10L);
|
||||
this.certificateHostNameOverride = builder.certificateHostNameOverride;
|
||||
this.maxChannelsPerEndpoint = builder.maxChannelsPerEndpoint;
|
||||
this.maxRequestsPerChannel = builder.maxRequestsPerChannel;
|
||||
this.connectionTimeout = builder.connectionTimeout == null ? builder.requestTimeout : builder.connectionTimeout;
|
||||
this.partitionCount = builder.partitionCount;
|
||||
this.requestTimeout = builder.requestTimeout;
|
||||
this.receiveHangDetectionTime = builder.receiveHangDetectionTime;
|
||||
this.sendHangDetectionTime = builder.sendHangDetectionTime;
|
||||
this.userAgent = builder.userAgent;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Property accessors
|
||||
// region Accessors
|
||||
|
||||
public String getCertificateHostNameOverride() {
|
||||
return certificateHostNameOverride;
|
||||
return this.certificateHostNameOverride;
|
||||
}
|
||||
|
||||
public void setCertificateHostNameOverride(String value) {
|
||||
this.certificateHostNameOverride = value;
|
||||
}
|
||||
|
||||
public int getMaxChannels() {
|
||||
return this.maxChannels;
|
||||
}
|
||||
|
||||
public void setMaxChannels(int value) {
|
||||
this.maxChannels = value;
|
||||
public int getMaxChannelsPerEndpoint() {
|
||||
return this.maxChannelsPerEndpoint;
|
||||
}
|
||||
|
||||
public int getMaxRequestsPerChannel() {
|
||||
return this.maxRequestsPerChannel;
|
||||
}
|
||||
|
||||
public void setMaxRequestsPerChannel(int maxRequestsPerChannel) {
|
||||
this.maxRequestsPerChannel = maxRequestsPerChannel;
|
||||
}
|
||||
|
||||
public Duration getOpenTimeout() {
|
||||
return this.openTimeout.isNegative() || this.openTimeout.isZero() ? this.requestTimeout : this.openTimeout;
|
||||
}
|
||||
|
||||
public void setOpenTimeout(Duration value) {
|
||||
this.openTimeout = value;
|
||||
public Duration getConnectionTimeout() {
|
||||
return this.connectionTimeout;
|
||||
}
|
||||
|
||||
public int getPartitionCount() {
|
||||
return this.partitionCount;
|
||||
}
|
||||
|
||||
public void setPartitionCount(int value) {
|
||||
this.partitionCount = value;
|
||||
}
|
||||
|
||||
public Duration getReceiveHangDetectionTime() {
|
||||
return this.receiveHangDetectionTime;
|
||||
}
|
||||
|
||||
public void setReceiveHangDetectionTime(Duration value) {
|
||||
this.receiveHangDetectionTime = value;
|
||||
}
|
||||
|
||||
public Duration getRequestTimeout() {
|
||||
return this.requestTimeout;
|
||||
}
|
||||
|
@ -477,63 +261,129 @@ final public class RntbdTransportClient extends TransportClient implements AutoC
|
|||
return this.sendHangDetectionTime;
|
||||
}
|
||||
|
||||
public void setSendHangDetectionTime(Duration value) {
|
||||
this.sendHangDetectionTime = value;
|
||||
}
|
||||
|
||||
public Duration getTimerPoolResolution() {
|
||||
return calculateTimerPoolResolutionSeconds(this.timerPoolResolution, this.requestTimeout, this.openTimeout);
|
||||
}
|
||||
|
||||
public void setTimerPoolResolution(Duration value) {
|
||||
this.timerPoolResolution = value;
|
||||
}
|
||||
|
||||
public UserAgentContainer getUserAgent() {
|
||||
|
||||
if (this.userAgent != null) {
|
||||
return this.userAgent;
|
||||
}
|
||||
|
||||
this.userAgent = new UserAgentContainer();
|
||||
return this.userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(UserAgentContainer value) {
|
||||
this.userAgent = value;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Methods
|
||||
|
||||
private static Duration calculateTimerPoolResolutionSeconds(
|
||||
@Override
|
||||
public String toString() {
|
||||
return RntbdObjectMapper.toJson(this);
|
||||
}
|
||||
|
||||
Duration timerPoolResolution,
|
||||
Duration requestTimeout,
|
||||
Duration openTimeout) {
|
||||
// endregion
|
||||
|
||||
Objects.requireNonNull(timerPoolResolution, "timerPoolResolution");
|
||||
Objects.requireNonNull(requestTimeout, "requestTimeout");
|
||||
Objects.requireNonNull(openTimeout, "openTimeout");
|
||||
// region Types
|
||||
|
||||
if (timerPoolResolution.compareTo(Duration.ZERO) <= 0 && requestTimeout.compareTo(Duration.ZERO) <= 0 &&
|
||||
openTimeout.compareTo(Duration.ZERO) <= 0) {
|
||||
public static class Builder {
|
||||
|
||||
throw new IllegalStateException("RntbdTransportClient.Options");
|
||||
// region Fields
|
||||
|
||||
private static final UserAgentContainer DEFAULT_USER_AGENT_CONTAINER = new UserAgentContainer();
|
||||
private static final Duration SIXTY_FIVE_SECONDS = Duration.ofSeconds(65L);
|
||||
private static final Duration TEN_SECONDS = Duration.ofSeconds(10L);
|
||||
|
||||
// Required parameters
|
||||
|
||||
private String certificateHostNameOverride = null;
|
||||
|
||||
// Optional parameters
|
||||
|
||||
private int maxChannelsPerEndpoint = 10;
|
||||
private int maxRequestsPerChannel = 30;
|
||||
private Duration connectionTimeout = null;
|
||||
private int partitionCount = 1;
|
||||
private Duration receiveHangDetectionTime = SIXTY_FIVE_SECONDS;
|
||||
private Duration requestTimeout;
|
||||
private Duration sendHangDetectionTime = TEN_SECONDS;
|
||||
private UserAgentContainer userAgent = DEFAULT_USER_AGENT_CONTAINER;
|
||||
|
||||
// endregion
|
||||
|
||||
// region Constructors
|
||||
|
||||
public Builder(Duration requestTimeout) {
|
||||
this.requestTimeout(requestTimeout);
|
||||
}
|
||||
|
||||
if (timerPoolResolution.compareTo(Duration.ZERO) > 0 && timerPoolResolution.compareTo(openTimeout) < 0 &&
|
||||
timerPoolResolution.compareTo(requestTimeout) < 0) {
|
||||
|
||||
return timerPoolResolution;
|
||||
public Builder(int requestTimeoutInSeconds) {
|
||||
this(Duration.ofSeconds(requestTimeoutInSeconds));
|
||||
}
|
||||
|
||||
if (openTimeout.compareTo(Duration.ZERO) > 0 && requestTimeout.compareTo(Duration.ZERO) > 0) {
|
||||
return openTimeout.compareTo(requestTimeout) < 0 ? openTimeout : requestTimeout;
|
||||
// endregion
|
||||
|
||||
// region Methods
|
||||
|
||||
public Options build() {
|
||||
return new Options(this);
|
||||
}
|
||||
|
||||
return openTimeout.compareTo(Duration.ZERO) > 0 ? openTimeout : requestTimeout;
|
||||
public Builder certificateHostNameOverride(final String value) {
|
||||
this.certificateHostNameOverride = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder connectionTimeout(final Duration value) {
|
||||
checkArgument(value == null || value.compareTo(Duration.ZERO) > 0, "value: %s", value);
|
||||
this.connectionTimeout = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxRequestsPerChannel(final int value) {
|
||||
checkArgument(value > 0, "value: %s", value);
|
||||
this.maxRequestsPerChannel = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxChannelsPerEndpoint(final int value) {
|
||||
checkArgument(value > 0, "value: %s", value);
|
||||
this.maxChannelsPerEndpoint = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder partitionCount(final int value) {
|
||||
checkArgument(value > 0, "value: %s", value);
|
||||
this.partitionCount = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder receiveHangDetectionTime(final Duration value) {
|
||||
|
||||
checkNotNull(value, "value: null");
|
||||
checkArgument(value.compareTo(Duration.ZERO) > 0, "value: %s", value);
|
||||
|
||||
this.receiveHangDetectionTime = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder requestTimeout(final Duration value) {
|
||||
|
||||
checkNotNull(value, "value: null");
|
||||
checkArgument(value.compareTo(Duration.ZERO) > 0, "value: %s", value);
|
||||
|
||||
this.requestTimeout = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder sendHangDetectionTime(final Duration value) {
|
||||
|
||||
checkNotNull(value, "value: null");
|
||||
checkArgument(value.compareTo(Duration.ZERO) > 0, "value: %s", value);
|
||||
|
||||
this.sendHangDetectionTime = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder userAgent(final UserAgentContainer value) {
|
||||
checkNotNull(value, "value: null");
|
||||
this.userAgent = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.pool.ChannelPool;
|
||||
import io.netty.channel.pool.ChannelPoolHandler;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class RntbdClientChannelHandler extends ChannelInitializer<Channel> implements ChannelPoolHandler {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RntbdClientChannelHandler.class);
|
||||
private final RntbdEndpoint.Config config;
|
||||
|
||||
RntbdClientChannelHandler(final RntbdEndpoint.Config config) {
|
||||
checkNotNull(config, "config");
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link ChannelPool#acquire} after a {@link Channel} is acquired
|
||||
* <p>
|
||||
* This method is called within the {@link EventLoop} of the {@link Channel}.
|
||||
*
|
||||
* @param channel a channel that was just acquired
|
||||
*/
|
||||
@Override
|
||||
public void channelAcquired(final Channel channel) throws Exception {
|
||||
logger.trace("{} CHANNEL ACQUIRED", channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link ChannelPool#release} after a {@link Channel} is created
|
||||
* <p>
|
||||
* This method is called within the {@link EventLoop} of the {@link Channel}.
|
||||
*
|
||||
* @param channel a channel that was just created
|
||||
*/
|
||||
@Override
|
||||
public void channelCreated(final Channel channel) throws Exception {
|
||||
logger.trace("{} CHANNEL CREATED", channel);
|
||||
this.initChannel(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link ChannelPool#release} after a {@link Channel} is released
|
||||
* <p>
|
||||
* This method is called within the {@link EventLoop} of the {@link Channel}.
|
||||
*
|
||||
* @param channel a channel that was just released
|
||||
*/
|
||||
@Override
|
||||
public void channelReleased(final Channel channel) throws Exception {
|
||||
logger.trace("{} CHANNEL RELEASED", channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by @{ChannelPipeline} initializer after the current channel is registered to an event loop.
|
||||
* <p>
|
||||
* This method constructs this pipeline:
|
||||
* <pre>{@code
|
||||
* ChannelPipeline {
|
||||
* (ReadTimeoutHandler#0 = io.netty.handler.timeout.ReadTimeoutHandler),
|
||||
* (SslHandler#0 = io.netty.handler.ssl.SslHandler),
|
||||
* (RntbdContextNegotiator#0 = com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdContextNegotiator),
|
||||
* (RntbdResponseDecoder#0 = com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdResponseDecoder),
|
||||
* (RntbdRequestEncoder#0 = com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestEncoder),
|
||||
* (WriteTimeoutHandler#0 = io.netty.handler.timeout.WriteTimeoutHandler),
|
||||
* (RntbdRequestManager#0 = com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestManager),
|
||||
* (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1)
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param channel a channel that was just registered with an event loop
|
||||
*/
|
||||
@Override
|
||||
protected void initChannel(final Channel channel) {
|
||||
|
||||
checkNotNull(channel);
|
||||
|
||||
assert channel.isRegistered();
|
||||
assert channel.isOpen();
|
||||
assert !channel.isActive();
|
||||
|
||||
final RntbdRequestManager requestManager = new RntbdRequestManager();
|
||||
final long readerIdleTime = this.config.getReceiveHangDetectionTime();
|
||||
final long writerIdleTime = this.config.getSendHangDetectionTime();
|
||||
final ChannelPipeline pipeline = channel.pipeline();
|
||||
|
||||
pipeline.addFirst(
|
||||
new RntbdContextNegotiator(requestManager, this.config.getUserAgent()),
|
||||
new RntbdResponseDecoder(),
|
||||
new RntbdRequestEncoder(),
|
||||
new WriteTimeoutHandler(writerIdleTime, TimeUnit.NANOSECONDS),
|
||||
requestManager
|
||||
);
|
||||
|
||||
if (this.config.getWireLogLevel() != null) {
|
||||
pipeline.addFirst(new LoggingHandler(this.config.getWireLogLevel()));
|
||||
}
|
||||
|
||||
final SSLEngine sslEngine = this.config.getSslContext().newEngine(channel.alloc());
|
||||
|
||||
pipeline.addFirst(
|
||||
new ReadTimeoutHandler(readerIdleTime, TimeUnit.NANOSECONDS),
|
||||
new SslHandler(sslEngine)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.microsoft.azure.cosmosdb.internal.UserAgentContainer;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.RntbdTransportClient.Options;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.util.Objects;
|
||||
|
||||
final public class RntbdClientChannelInitializer extends ChannelInitializer<NioSocketChannel> {
|
||||
|
||||
private final LogLevel logLevel;
|
||||
private final Options options;
|
||||
private final RntbdRequestManager requestManager;
|
||||
private final SslContext sslContext;
|
||||
private final UserAgentContainer userAgent;
|
||||
|
||||
public RntbdClientChannelInitializer(
|
||||
UserAgentContainer userAgent,
|
||||
SslContext sslContext,
|
||||
LogLevel logLevel,
|
||||
Options options
|
||||
) {
|
||||
|
||||
Objects.requireNonNull(sslContext, "sslContext");
|
||||
Objects.requireNonNull(userAgent, "userAgent");
|
||||
Objects.requireNonNull(options, "options");
|
||||
|
||||
this.requestManager = new RntbdRequestManager();
|
||||
this.sslContext = sslContext;
|
||||
this.userAgent = userAgent;
|
||||
this.logLevel = logLevel;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public RntbdRequestManager getRequestManager() {
|
||||
return this.requestManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
|
||||
final ChannelPipeline pipeline = channel.pipeline();
|
||||
|
||||
pipeline.addFirst(
|
||||
new RntbdContextNegotiator(this.requestManager, this.userAgent),
|
||||
new RntbdResponseDecoder(),
|
||||
new RntbdRequestEncoder(),
|
||||
this.requestManager
|
||||
);
|
||||
|
||||
if (this.logLevel != null) {
|
||||
pipeline.addFirst(new LoggingHandler(this.logLevel));
|
||||
}
|
||||
|
||||
final int readerIdleTime = (int)this.options.getReceiveHangDetectionTime().toNanos();
|
||||
final int writerIdleTime = (int)this.options.getSendHangDetectionTime().toNanos();
|
||||
final SSLEngine sslEngine = this.sslContext.newEngine(channel.alloc());
|
||||
|
||||
pipeline.addFirst(
|
||||
// TODO: DANOBLE: Utilize Read/WriteTimeoutHandler for receive/send hang detection
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/331552
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/331593
|
||||
// Notes:
|
||||
// First (naive?) attempt caused performance degradation
|
||||
// new WriteTimeoutHandler(writerIdleTime),
|
||||
// new ReadTimeoutHandler(readerIdleTime),
|
||||
new SslHandler(sslEngine)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelId;
|
||||
import io.netty.channel.pool.ChannelHealthChecker;
|
||||
import io.netty.channel.pool.FixedChannelPool;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
@JsonSerialize(using = RntbdClientChannelPool.JsonSerializer.class)
|
||||
public final class RntbdClientChannelPool extends FixedChannelPool {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdClientChannelPool.class);
|
||||
private static final AtomicReference<Field> pendingAcquireCount = new AtomicReference<>();
|
||||
|
||||
private final ConcurrentHashMap<ChannelId, Channel> atCapacity;
|
||||
private final AtomicInteger availableChannelCount;
|
||||
private final AtomicBoolean closed;
|
||||
private final int maxRequestsPerChannel;
|
||||
|
||||
/**
|
||||
* Creates a new instance using the {@link ChannelHealthChecker#ACTIVE}
|
||||
*
|
||||
* @param bootstrap the {@link Bootstrap} that is used for connections
|
||||
* @param config the {@link RntbdEndpoint.Config} that is used for the channel pool instance created
|
||||
*/
|
||||
RntbdClientChannelPool(final Bootstrap bootstrap, final RntbdEndpoint.Config config) {
|
||||
|
||||
super(bootstrap, new RntbdClientChannelHandler(config), ChannelHealthChecker.ACTIVE, null,
|
||||
-1L, config.getMaxChannelsPerEndpoint(), Integer.MAX_VALUE, true
|
||||
);
|
||||
|
||||
this.maxRequestsPerChannel = config.getMaxRequestsPerChannel();
|
||||
this.availableChannelCount = new AtomicInteger();
|
||||
this.atCapacity = new ConcurrentHashMap<>();
|
||||
this.closed = new AtomicBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Channel> acquire(Promise<Channel> promise) {
|
||||
this.throwIfClosed();
|
||||
return super.acquire(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> release(Channel channel, Promise<Void> promise) {
|
||||
this.throwIfClosed();
|
||||
return super.release(channel, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
if (this.closed.compareAndSet(false, true)) {
|
||||
|
||||
if (!this.atCapacity.isEmpty()) {
|
||||
|
||||
for (Channel channel : this.atCapacity.values()) {
|
||||
super.offerChannel(channel);
|
||||
}
|
||||
|
||||
this.atCapacity.clear();
|
||||
}
|
||||
|
||||
this.availableChannelCount.set(0);
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
public int availableChannelCount() {
|
||||
return this.availableChannelCount.get();
|
||||
}
|
||||
|
||||
public int pendingAcquireCount() {
|
||||
|
||||
Field field = pendingAcquireCount.get();
|
||||
|
||||
if (field == null) {
|
||||
|
||||
synchronized (pendingAcquireCount) {
|
||||
|
||||
field = pendingAcquireCount.get();
|
||||
|
||||
if (field == null) {
|
||||
field = FieldUtils.getDeclaredField(FixedChannelPool.class, "pendingAcquireCount", true);
|
||||
pendingAcquireCount.set(field);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
return (int)FieldUtils.readField(field, this);
|
||||
} catch (IllegalAccessException error) {
|
||||
logger.error("could not access field due to ", error);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll a {@link Channel} out of internal storage to reuse it
|
||||
* <p>
|
||||
* Maintainers: Implementations of this method must be thread-safe.
|
||||
*
|
||||
* @return a value of {@code null}, if no {@link Channel} is ready to be reused
|
||||
*/
|
||||
@Override
|
||||
protected synchronized Channel pollChannel() {
|
||||
|
||||
final Channel channel = super.pollChannel();
|
||||
|
||||
if (channel != null) {
|
||||
this.availableChannelCount.decrementAndGet();
|
||||
return channel;
|
||||
}
|
||||
|
||||
if (this.atCapacity.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.atCapacity.search(Long.MAX_VALUE, (id, value) -> {
|
||||
if (pendingRequestCount(value) < this.maxRequestsPerChannel) {
|
||||
this.availableChannelCount.decrementAndGet();
|
||||
this.atCapacity.remove(id);
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer a {@link Channel} back to the internal storage
|
||||
* <p>
|
||||
* Maintainers: Implementations of this method must be thread-safe.
|
||||
*
|
||||
* @param channel the {@link Channel} to return to internal storage
|
||||
* @return {@code true}, if the {@link Channel} could be added to internal storage; otherwise {@code false}
|
||||
*/
|
||||
@Override
|
||||
protected synchronized boolean offerChannel(final Channel channel) {
|
||||
|
||||
checkArgument(channel.isActive(), "%s inactive", channel);
|
||||
final boolean offered;
|
||||
|
||||
if (pendingRequestCount(channel) >= this.maxRequestsPerChannel) {
|
||||
this.atCapacity.put(channel.id(), channel);
|
||||
offered = true;
|
||||
} else {
|
||||
offered = super.offerChannel(channel);
|
||||
}
|
||||
|
||||
if (offered) {
|
||||
this.availableChannelCount.incrementAndGet();
|
||||
}
|
||||
|
||||
return offered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return RntbdObjectMapper.toJson(this);
|
||||
}
|
||||
|
||||
private static int pendingRequestCount(final Channel channel) {
|
||||
return channel.pipeline().get(RntbdRequestManager.class).getPendingRequestCount();
|
||||
}
|
||||
|
||||
private void throwIfClosed() {
|
||||
checkState(!this.closed.get(), "%s is closed", this);
|
||||
}
|
||||
|
||||
// region Types
|
||||
|
||||
static final class JsonSerializer extends StdSerializer<RntbdClientChannelPool> {
|
||||
|
||||
public JsonSerializer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public JsonSerializer(Class<RntbdClientChannelPool> type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(RntbdClientChannelPool value, JsonGenerator generator, SerializerProvider provider) throws IOException {
|
||||
|
||||
generator.writeStartObject();
|
||||
generator.writeNumberField("acquiredChannelCount", value.acquiredChannelCount());
|
||||
generator.writeNumberField("availableChannelCount", value.availableChannelCount());
|
||||
generator.writeNumberField("maxRequestsPerChannel", value.maxRequestsPerChannel);
|
||||
generator.writeNumberField("pendingAcquisitionCount", value.pendingAcquireCount());
|
||||
generator.writeBooleanField("isClosed", value.closed.get());
|
||||
generator.writeEndObject();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,12 +50,12 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdConsistencyLevel(byte id) {
|
||||
RntbdConsistencyLevel(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,12 +68,12 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdContentSerializationFormat(byte id) {
|
||||
RntbdContentSerializationFormat(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ final class RntbdConstants {
|
|||
public static final ImmutableSet<RntbdContextHeader> set = Sets.immutableEnumSet(EnumSet.allOf(RntbdContextHeader.class));
|
||||
|
||||
static {
|
||||
Collector<RntbdContextHeader, ?, ImmutableMap<Short, RntbdContextHeader>> collector = ImmutableMap.toImmutableMap(RntbdContextHeader::id, h -> h);
|
||||
final Collector<RntbdContextHeader, ?, ImmutableMap<Short, RntbdContextHeader>> collector = ImmutableMap.toImmutableMap(RntbdContextHeader::id, h -> h);
|
||||
map = set.stream().collect(collector);
|
||||
}
|
||||
|
||||
|
@ -98,20 +98,20 @@ final class RntbdConstants {
|
|||
private final boolean isRequired;
|
||||
private final RntbdTokenType type;
|
||||
|
||||
RntbdContextHeader(short id, RntbdTokenType type, boolean isRequired) {
|
||||
RntbdContextHeader(final short id, final RntbdTokenType type, final boolean isRequired) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.isRequired = isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return this.isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public RntbdTokenType type() {
|
||||
return this.type;
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ final class RntbdConstants {
|
|||
public static final ImmutableSet<RntbdContextRequestHeader> set = Sets.immutableEnumSet(EnumSet.allOf(RntbdContextRequestHeader.class));
|
||||
|
||||
static {
|
||||
Collector<RntbdContextRequestHeader, ?, ImmutableMap<Short, RntbdContextRequestHeader>> collector = ImmutableMap.toImmutableMap(h -> h.id(), h -> h);
|
||||
final Collector<RntbdContextRequestHeader, ?, ImmutableMap<Short, RntbdContextRequestHeader>> collector = ImmutableMap.toImmutableMap(h -> h.id(), h -> h);
|
||||
map = set.stream().collect(collector);
|
||||
}
|
||||
|
||||
|
@ -135,20 +135,20 @@ final class RntbdConstants {
|
|||
private final boolean isRequired;
|
||||
private final RntbdTokenType type;
|
||||
|
||||
RntbdContextRequestHeader(short id, RntbdTokenType type, boolean isRequired) {
|
||||
RntbdContextRequestHeader(final short id, final RntbdTokenType type, final boolean isRequired) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.isRequired = isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return this.isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public RntbdTokenType type() {
|
||||
return this.type;
|
||||
}
|
||||
|
@ -163,12 +163,12 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdEnumerationDirection(byte id) {
|
||||
RntbdEnumerationDirection(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,12 +179,12 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdFanoutOperationState(byte id) {
|
||||
RntbdFanoutOperationState(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,11 +197,11 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdIndexingDirective(byte id) {
|
||||
RntbdIndexingDirective(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static RntbdIndexingDirective fromId(byte id) {
|
||||
public static RntbdIndexingDirective fromId(final byte id) {
|
||||
switch (id) {
|
||||
case (byte)0x00:
|
||||
return Default;
|
||||
|
@ -216,7 +216,7 @@ final class RntbdConstants {
|
|||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,12 +229,12 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdMigrateCollectionDirective(byte id) {
|
||||
RntbdMigrateCollectionDirective(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,11 +278,11 @@ final class RntbdConstants {
|
|||
|
||||
private final short id;
|
||||
|
||||
RntbdOperationType(short id) {
|
||||
RntbdOperationType(final short id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static RntbdOperationType fromId(short id) throws IllegalArgumentException {
|
||||
public static RntbdOperationType fromId(final short id) throws IllegalArgumentException {
|
||||
|
||||
switch (id) {
|
||||
case 0x0000:
|
||||
|
@ -359,7 +359,7 @@ final class RntbdConstants {
|
|||
}
|
||||
|
||||
public short id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,12 +371,12 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdReadFeedKeyType(byte id) {
|
||||
RntbdReadFeedKeyType(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,12 +389,12 @@ final class RntbdConstants {
|
|||
|
||||
private final byte id;
|
||||
|
||||
RntbdRemoteStorageType(byte id) {
|
||||
RntbdRemoteStorageType(final byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,7 +510,7 @@ final class RntbdConstants {
|
|||
public static final ImmutableSet<RntbdRequestHeader> set = Sets.immutableEnumSet(EnumSet.allOf(RntbdRequestHeader.class));
|
||||
|
||||
static {
|
||||
Collector<RntbdRequestHeader, ?, ImmutableMap<Short, RntbdRequestHeader>> collector = ImmutableMap.toImmutableMap(RntbdRequestHeader::id, h -> h);
|
||||
final Collector<RntbdRequestHeader, ?, ImmutableMap<Short, RntbdRequestHeader>> collector = ImmutableMap.toImmutableMap(RntbdRequestHeader::id, h -> h);
|
||||
map = set.stream().collect(collector);
|
||||
}
|
||||
|
||||
|
@ -518,20 +518,20 @@ final class RntbdConstants {
|
|||
private final boolean isRequired;
|
||||
private final RntbdTokenType type;
|
||||
|
||||
RntbdRequestHeader(short id, RntbdTokenType type, boolean isRequired) {
|
||||
RntbdRequestHeader(final short id, final RntbdTokenType type, final boolean isRequired) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.isRequired = isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return this.isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public RntbdTokenType type() {
|
||||
return this.type;
|
||||
}
|
||||
|
@ -572,11 +572,11 @@ final class RntbdConstants {
|
|||
|
||||
private final short id;
|
||||
|
||||
RntbdResourceType(short id) {
|
||||
RntbdResourceType(final short id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static RntbdResourceType fromId(short id) throws IllegalArgumentException {
|
||||
public static RntbdResourceType fromId(final short id) throws IllegalArgumentException {
|
||||
switch (id) {
|
||||
case 0x0000:
|
||||
return RntbdResourceType.Connection;
|
||||
|
@ -642,7 +642,7 @@ final class RntbdConstants {
|
|||
}
|
||||
|
||||
public short id() {
|
||||
return id;
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -710,7 +710,7 @@ final class RntbdConstants {
|
|||
public static final ImmutableSet<RntbdResponseHeader> set = Sets.immutableEnumSet(EnumSet.allOf(RntbdResponseHeader.class));
|
||||
|
||||
static {
|
||||
Collector<RntbdResponseHeader, ?, ImmutableMap<Short, RntbdResponseHeader>> collector = ImmutableMap.toImmutableMap(RntbdResponseHeader::id, header -> header);
|
||||
final Collector<RntbdResponseHeader, ?, ImmutableMap<Short, RntbdResponseHeader>> collector = ImmutableMap.toImmutableMap(RntbdResponseHeader::id, header -> header);
|
||||
map = set.stream().collect(collector);
|
||||
}
|
||||
|
||||
|
@ -718,20 +718,20 @@ final class RntbdConstants {
|
|||
private final boolean isRequired;
|
||||
private final RntbdTokenType type;
|
||||
|
||||
RntbdResponseHeader(short id, RntbdTokenType type, boolean isRequired) {
|
||||
RntbdResponseHeader(final short id, final RntbdTokenType type, final boolean isRequired) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.isRequired = isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return this.isRequired;
|
||||
}
|
||||
|
||||
public short id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public RntbdTokenType type() {
|
||||
return this.type;
|
||||
}
|
||||
|
@ -739,10 +739,10 @@ final class RntbdConstants {
|
|||
|
||||
interface RntbdHeader {
|
||||
|
||||
short id();
|
||||
|
||||
boolean isRequired();
|
||||
|
||||
short id();
|
||||
|
||||
String name();
|
||||
|
||||
RntbdTokenType type();
|
||||
|
|
|
@ -41,85 +41,17 @@ import java.util.UUID;
|
|||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.CurrentProtocolVersion;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.RntbdContextHeader;
|
||||
|
||||
final public class RntbdContext {
|
||||
public final class RntbdContext {
|
||||
|
||||
final private RntbdResponseStatus frame;
|
||||
final private Headers headers;
|
||||
private final RntbdResponseStatus frame;
|
||||
private final Headers headers;
|
||||
|
||||
private RntbdContext(RntbdResponseStatus frame, Headers headers) {
|
||||
private RntbdContext(final RntbdResponseStatus frame, final Headers headers) {
|
||||
|
||||
this.frame = frame;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
static RntbdContext decode(ByteBuf in) throws TransportException {
|
||||
|
||||
in.markReaderIndex();
|
||||
|
||||
final RntbdResponseStatus frame = RntbdResponseStatus.decode(in);
|
||||
final int statusCode = frame.getStatusCode();
|
||||
final int headersLength = frame.getHeadersLength();
|
||||
|
||||
if (statusCode < 200 || statusCode >= 400) {
|
||||
if (!RntbdFramer.canDecodePayload(in, in.readerIndex() + headersLength)) {
|
||||
in.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final Headers headers = Headers.decode(in.readSlice(headersLength));
|
||||
|
||||
if (statusCode < 200 || statusCode >= 400) {
|
||||
|
||||
final ObjectNode details = (ObjectNode)RntbdObjectMapper.readTree(in.readSlice(in.readIntLE()));
|
||||
final HashMap<String, Object> map = new HashMap<>(4);
|
||||
|
||||
if (headers.clientVersion.isPresent()) {
|
||||
map.put("requiredClientVersion", headers.clientVersion.getValue());
|
||||
}
|
||||
|
||||
if (headers.protocolVersion.isPresent()) {
|
||||
map.put("requiredProtocolVersion", headers.protocolVersion.getValue());
|
||||
}
|
||||
|
||||
if (headers.serverAgent.isPresent()) {
|
||||
map.put("serverAgent", headers.serverAgent.getValue());
|
||||
}
|
||||
|
||||
if (headers.serverVersion.isPresent()) {
|
||||
map.put("serverVersion", headers.serverVersion.getValue());
|
||||
}
|
||||
|
||||
throw new TransportException(frame.getStatus(), details, Collections.unmodifiableMap(map));
|
||||
}
|
||||
|
||||
return new RntbdContext(frame, headers);
|
||||
}
|
||||
|
||||
public static RntbdContext from(RntbdContextRequest request, ServerProperties properties, HttpResponseStatus status) {
|
||||
|
||||
// NOTE TO CODE REVIEWERS
|
||||
// ----------------------
|
||||
// In its current form this method is meant to enable a limited set of test scenarios. It will be revised as
|
||||
// required to support test scenarios as they are developed.
|
||||
|
||||
final Headers headers = new Headers();
|
||||
|
||||
headers.clientVersion.setValue(request.getClientVersion());
|
||||
headers.idleTimeoutInSeconds.setValue(0);
|
||||
headers.protocolVersion.setValue(CurrentProtocolVersion);
|
||||
headers.serverAgent.setValue(properties.getAgent());
|
||||
headers.serverVersion.setValue(properties.getVersion());
|
||||
headers.unauthenticatedTimeoutInSeconds.setValue(0);
|
||||
|
||||
final int length = RntbdResponseStatus.LENGTH + headers.computeLength();
|
||||
final UUID activityId = request.getActivityId();
|
||||
|
||||
final RntbdResponseStatus frame = new RntbdResponseStatus(length, status, activityId);
|
||||
|
||||
return new RntbdContext(frame, headers);
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
UUID getActivityId() {
|
||||
return this.frame.getActivityId();
|
||||
|
@ -162,31 +94,99 @@ final public class RntbdContext {
|
|||
return this.headers.unauthenticatedTimeoutInSeconds.getValue(Long.class);
|
||||
}
|
||||
|
||||
public void encode(ByteBuf out) {
|
||||
static RntbdContext decode(final ByteBuf in) throws TransportException {
|
||||
|
||||
int start = out.writerIndex();
|
||||
in.markReaderIndex();
|
||||
|
||||
final RntbdResponseStatus frame = RntbdResponseStatus.decode(in);
|
||||
final int statusCode = frame.getStatusCode();
|
||||
final int headersLength = frame.getHeadersLength();
|
||||
|
||||
if (statusCode < 200 || statusCode >= 400) {
|
||||
if (!RntbdFramer.canDecodePayload(in, in.readerIndex() + headersLength)) {
|
||||
in.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final Headers headers = Headers.decode(in.readSlice(headersLength));
|
||||
|
||||
if (statusCode < 200 || statusCode >= 400) {
|
||||
|
||||
final ObjectNode details = RntbdObjectMapper.readTree(in.readSlice(in.readIntLE()));
|
||||
final HashMap<String, Object> map = new HashMap<>(4);
|
||||
|
||||
if (headers.clientVersion.isPresent()) {
|
||||
map.put("requiredClientVersion", headers.clientVersion.getValue());
|
||||
}
|
||||
|
||||
if (headers.protocolVersion.isPresent()) {
|
||||
map.put("requiredProtocolVersion", headers.protocolVersion.getValue());
|
||||
}
|
||||
|
||||
if (headers.serverAgent.isPresent()) {
|
||||
map.put("serverAgent", headers.serverAgent.getValue());
|
||||
}
|
||||
|
||||
if (headers.serverVersion.isPresent()) {
|
||||
map.put("serverVersion", headers.serverVersion.getValue());
|
||||
}
|
||||
|
||||
throw new TransportException(frame.getStatus(), details, Collections.unmodifiableMap(map));
|
||||
}
|
||||
|
||||
return new RntbdContext(frame, headers);
|
||||
}
|
||||
|
||||
public void encode(final ByteBuf out) {
|
||||
|
||||
final int start = out.writerIndex();
|
||||
|
||||
this.frame.encode(out);
|
||||
this.headers.encode(out);
|
||||
|
||||
int length = out.writerIndex() - start;
|
||||
final int length = out.writerIndex() - start;
|
||||
|
||||
if (length != this.frame.getLength()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public static RntbdContext from(final RntbdContextRequest request, final ServerProperties properties, final HttpResponseStatus status) {
|
||||
|
||||
// NOTE TO CODE REVIEWERS
|
||||
// ----------------------
|
||||
// In its current form this method is meant to enable a limited set of test scenarios. It will be revised as
|
||||
// required to support test scenarios as they are developed.
|
||||
|
||||
final Headers headers = new Headers();
|
||||
|
||||
headers.clientVersion.setValue(request.getClientVersion());
|
||||
headers.idleTimeoutInSeconds.setValue(0);
|
||||
headers.protocolVersion.setValue(CurrentProtocolVersion);
|
||||
headers.serverAgent.setValue(properties.getAgent());
|
||||
headers.serverVersion.setValue(properties.getVersion());
|
||||
headers.unauthenticatedTimeoutInSeconds.setValue(0);
|
||||
|
||||
final int length = RntbdResponseStatus.LENGTH + headers.computeLength();
|
||||
final UUID activityId = request.getActivityId();
|
||||
|
||||
final RntbdResponseStatus frame = new RntbdResponseStatus(length, status, activityId);
|
||||
|
||||
return new RntbdContext(frame, headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
final ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
try {
|
||||
return writer.writeValueAsString(this);
|
||||
} catch (JsonProcessingException error) {
|
||||
} catch (final JsonProcessingException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
}
|
||||
|
||||
final private static class Headers extends RntbdTokenStream<RntbdContextHeader> {
|
||||
private static final class Headers extends RntbdTokenStream<RntbdContextHeader> {
|
||||
|
||||
RntbdToken clientVersion;
|
||||
RntbdToken idleTimeoutInSeconds;
|
||||
|
@ -207,8 +207,8 @@ final public class RntbdContext {
|
|||
this.unauthenticatedTimeoutInSeconds = this.get(RntbdContextHeader.UnauthenticatedTimeoutInSeconds);
|
||||
}
|
||||
|
||||
static Headers decode(ByteBuf in) {
|
||||
Headers headers = new Headers();
|
||||
static Headers decode(final ByteBuf in) {
|
||||
final Headers headers = new Headers();
|
||||
Headers.decode(in, headers);
|
||||
return headers;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import java.util.List;
|
|||
|
||||
class RntbdContextDecoder extends ByteToMessageDecoder {
|
||||
|
||||
final private static Logger logger = LoggerFactory.getLogger("RntbdContextDecoder");
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdContextDecoder.class);
|
||||
|
||||
/**
|
||||
* Deserialize from an input {@link ByteBuf} to an {@link RntbdContext} instance
|
||||
|
@ -48,11 +48,11 @@ class RntbdContextDecoder extends ByteToMessageDecoder {
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) throws Exception {
|
||||
protected void decode(final ChannelHandlerContext context, final ByteBuf in, final List<Object> out) throws Exception {
|
||||
|
||||
if (RntbdFramer.canDecodeHead(in)) {
|
||||
|
||||
RntbdContext rntbdContext = RntbdContext.decode(in);
|
||||
final RntbdContext rntbdContext = RntbdContext.decode(in);
|
||||
context.fireUserEventTriggered(rntbdContext);
|
||||
in.discardReadBytes();
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.microsoft.azure.cosmosdb.internal.UserAgentContainer;
|
||||
import com.microsoft.azure.cosmosdb.internal.Utils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
|
@ -35,11 +36,12 @@ import io.netty.channel.CombinedChannelDuplexHandler;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
final public class RntbdContextNegotiator extends CombinedChannelDuplexHandler<RntbdContextDecoder, RntbdContextRequestEncoder> {
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public final class RntbdContextNegotiator extends CombinedChannelDuplexHandler<RntbdContextDecoder, RntbdContextRequestEncoder> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdContextNegotiator.class);
|
||||
private final RntbdRequestManager manager;
|
||||
|
@ -47,12 +49,12 @@ final public class RntbdContextNegotiator extends CombinedChannelDuplexHandler<R
|
|||
|
||||
private volatile boolean pendingRntbdContextRequest = true;
|
||||
|
||||
public RntbdContextNegotiator(RntbdRequestManager manager, UserAgentContainer userAgent) {
|
||||
public RntbdContextNegotiator(final RntbdRequestManager manager, final UserAgentContainer userAgent) {
|
||||
|
||||
super(new RntbdContextDecoder(), new RntbdContextRequestEncoder());
|
||||
|
||||
Objects.requireNonNull(manager);
|
||||
Objects.requireNonNull(userAgent);
|
||||
checkNotNull(manager, "manager");
|
||||
checkNotNull(userAgent, "userAgent");
|
||||
|
||||
this.manager = manager;
|
||||
this.userAgent = userAgent;
|
||||
|
@ -69,12 +71,11 @@ final public class RntbdContextNegotiator extends CombinedChannelDuplexHandler<R
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object message, ChannelPromise promise) throws Exception {
|
||||
|
||||
if (!(message instanceof ByteBuf)) {
|
||||
throw new IllegalArgumentException(String.format("message: %s", message.getClass()));
|
||||
}
|
||||
public void write(
|
||||
final ChannelHandlerContext context, final Object message, final ChannelPromise promise
|
||||
) throws Exception {
|
||||
|
||||
checkArgument(message instanceof ByteBuf, "message: %s", message.getClass());
|
||||
final ByteBuf out = (ByteBuf)message;
|
||||
|
||||
if (this.manager.hasRntbdContext()) {
|
||||
|
@ -91,12 +92,12 @@ final public class RntbdContextNegotiator extends CombinedChannelDuplexHandler<R
|
|||
|
||||
// region Privates
|
||||
|
||||
private void startRntbdContextRequest(ChannelHandlerContext context) throws Exception {
|
||||
private void startRntbdContextRequest(final ChannelHandlerContext context) throws Exception {
|
||||
|
||||
logger.debug("{} START CONTEXT REQUEST", context.channel());
|
||||
|
||||
final Channel channel = context.channel();
|
||||
final RntbdContextRequest request = new RntbdContextRequest(UUID.randomUUID(), this.userAgent);
|
||||
final RntbdContextRequest request = new RntbdContextRequest(Utils.randomUUID(), this.userAgent);
|
||||
final CompletableFuture<RntbdContextRequest> contextRequestFuture = this.manager.getRntbdContextRequestFuture();
|
||||
|
||||
super.write(context, request, channel.newPromise().addListener((ChannelFutureListener)future -> {
|
||||
|
|
|
@ -40,42 +40,50 @@ import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.Rnt
|
|||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.RntbdOperationType;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.RntbdResourceType;
|
||||
|
||||
final public class RntbdContextRequest {
|
||||
public final class RntbdContextRequest {
|
||||
|
||||
@JsonProperty
|
||||
final private UUID activityId;
|
||||
private final UUID activityId;
|
||||
|
||||
@JsonProperty
|
||||
final private Headers headers;
|
||||
private final Headers headers;
|
||||
|
||||
RntbdContextRequest(UUID activityId, UserAgentContainer userAgent) {
|
||||
RntbdContextRequest(final UUID activityId, final UserAgentContainer userAgent) {
|
||||
this(activityId, new Headers(userAgent));
|
||||
}
|
||||
|
||||
private RntbdContextRequest(UUID activityId, Headers headers) {
|
||||
private RntbdContextRequest(final UUID activityId, final Headers headers) {
|
||||
this.activityId = activityId;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public static RntbdContextRequest decode(ByteBuf in) {
|
||||
public UUID getActivityId() {
|
||||
return this.activityId;
|
||||
}
|
||||
|
||||
int resourceOperationTypeCode = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
public String getClientVersion() {
|
||||
return this.headers.clientVersion.getValue(String.class);
|
||||
}
|
||||
|
||||
public static RntbdContextRequest decode(final ByteBuf in) {
|
||||
|
||||
final int resourceOperationTypeCode = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
|
||||
if (resourceOperationTypeCode != 0) {
|
||||
String reason = String.format("resourceOperationCode=0x%08X", resourceOperationTypeCode);
|
||||
final String reason = String.format("resourceOperationCode=0x%08X", resourceOperationTypeCode);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
int start = in.readerIndex();
|
||||
int expectedLength = in.readIntLE();
|
||||
final int start = in.readerIndex();
|
||||
final int expectedLength = in.readIntLE();
|
||||
|
||||
RntbdRequestFrame header = RntbdRequestFrame.decode(in);
|
||||
Headers headers = Headers.decode(in.readSlice(expectedLength - (in.readerIndex() - start)));
|
||||
final RntbdRequestFrame header = RntbdRequestFrame.decode(in);
|
||||
final Headers headers = Headers.decode(in.readSlice(expectedLength - (in.readerIndex() - start)));
|
||||
|
||||
int observedLength = in.readerIndex() - start;
|
||||
final int observedLength = in.readerIndex() - start;
|
||||
|
||||
if (observedLength != expectedLength) {
|
||||
String reason = String.format("expectedLength=%d, observeredLength=%d", expectedLength, observedLength);
|
||||
final String reason = String.format("expectedLength=%d, observeredLength=%d", expectedLength, observedLength);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
|
@ -83,46 +91,38 @@ final public class RntbdContextRequest {
|
|||
return new RntbdContextRequest(header.getActivityId(), headers);
|
||||
}
|
||||
|
||||
public UUID getActivityId() {
|
||||
return activityId;
|
||||
}
|
||||
public void encode(final ByteBuf out) {
|
||||
|
||||
public String getClientVersion() {
|
||||
return this.headers.clientVersion.getValue(String.class);
|
||||
}
|
||||
|
||||
public void encode(ByteBuf out) {
|
||||
|
||||
int expectedLength = RntbdRequestFrame.LENGTH + headers.computeLength();
|
||||
int start = out.writerIndex();
|
||||
final int expectedLength = RntbdRequestFrame.LENGTH + this.headers.computeLength();
|
||||
final int start = out.writerIndex();
|
||||
|
||||
out.writeIntLE(expectedLength);
|
||||
|
||||
RntbdRequestFrame header = new RntbdRequestFrame(this.getActivityId(), RntbdOperationType.Connection, RntbdResourceType.Connection);
|
||||
final RntbdRequestFrame header = new RntbdRequestFrame(this.getActivityId(), RntbdOperationType.Connection, RntbdResourceType.Connection);
|
||||
header.encode(out);
|
||||
this.headers.encode(out);
|
||||
|
||||
int observedLength = out.writerIndex() - start;
|
||||
final int observedLength = out.writerIndex() - start;
|
||||
|
||||
if (observedLength != expectedLength) {
|
||||
String reason = String.format("expectedLength=%d, observeredLength=%d", expectedLength, observedLength);
|
||||
final String reason = String.format("expectedLength=%d, observeredLength=%d", expectedLength, observedLength);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
final ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
try {
|
||||
return writer.writeValueAsString(this);
|
||||
} catch (JsonProcessingException error) {
|
||||
} catch (final JsonProcessingException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
}
|
||||
|
||||
final private static class Headers extends RntbdTokenStream<RntbdContextRequestHeader> {
|
||||
private static final class Headers extends RntbdTokenStream<RntbdContextRequestHeader> {
|
||||
|
||||
private final static byte[] ClientVersion = Versions.CURRENT_VERSION.getBytes(StandardCharsets.UTF_8);
|
||||
private static final byte[] ClientVersion = Versions.CURRENT_VERSION.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
@JsonProperty
|
||||
RntbdToken clientVersion;
|
||||
|
@ -133,7 +133,7 @@ final public class RntbdContextRequest {
|
|||
@JsonProperty
|
||||
RntbdToken userAgent;
|
||||
|
||||
Headers(UserAgentContainer container) {
|
||||
Headers(final UserAgentContainer container) {
|
||||
|
||||
this();
|
||||
|
||||
|
@ -151,7 +151,7 @@ final public class RntbdContextRequest {
|
|||
this.userAgent = this.get(RntbdContextRequestHeader.UserAgent);
|
||||
}
|
||||
|
||||
static Headers decode(ByteBuf in) {
|
||||
static Headers decode(final ByteBuf in) {
|
||||
return Headers.decode(in, new Headers());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,12 +44,12 @@ public class RntbdContextRequestDecoder extends ByteToMessageDecoder {
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext context, Object message) throws Exception {
|
||||
public void channelRead(final ChannelHandlerContext context, final Object message) throws Exception {
|
||||
|
||||
if (message instanceof ByteBuf) {
|
||||
|
||||
ByteBuf in = (ByteBuf)message;
|
||||
int resourceOperationType = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
final ByteBuf in = (ByteBuf)message;
|
||||
final int resourceOperationType = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
|
||||
if (resourceOperationType == 0) {
|
||||
assert this.isSingleDecode();
|
||||
|
@ -72,14 +72,14 @@ public class RntbdContextRequestDecoder extends ByteToMessageDecoder {
|
|||
* @throws IllegalStateException thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) throws IllegalStateException {
|
||||
protected void decode(final ChannelHandlerContext context, final ByteBuf in, final List<Object> out) throws IllegalStateException {
|
||||
|
||||
RntbdContextRequest request;
|
||||
final RntbdContextRequest request;
|
||||
in.markReaderIndex();
|
||||
|
||||
try {
|
||||
request = RntbdContextRequest.decode(in);
|
||||
} catch (IllegalStateException error) {
|
||||
} catch (final IllegalStateException error) {
|
||||
in.resetReaderIndex();
|
||||
throw error;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
final class RntbdContextRequestEncoder extends MessageToByteEncoder {
|
||||
|
||||
final private static Logger Logger = LoggerFactory.getLogger(RntbdContextRequestEncoder.class);
|
||||
private static final Logger Logger = LoggerFactory.getLogger(RntbdContextRequestEncoder.class);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given message is an @{link RntbdContextRequest} instance
|
||||
|
@ -41,10 +41,9 @@ final class RntbdContextRequestEncoder extends MessageToByteEncoder {
|
|||
*
|
||||
* @param message the message to encode
|
||||
* @return @{code true}, if the given message is an an @{link RntbdContextRequest} instance; otherwise @{false}
|
||||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public boolean acceptOutboundMessage(Object message) throws Exception {
|
||||
public boolean acceptOutboundMessage(final Object message) {
|
||||
return message instanceof RntbdContextRequest;
|
||||
}
|
||||
|
||||
|
@ -59,14 +58,14 @@ final class RntbdContextRequestEncoder extends MessageToByteEncoder {
|
|||
* @throws IllegalStateException is thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext context, Object message, ByteBuf out) throws IllegalStateException {
|
||||
protected void encode(final ChannelHandlerContext context, final Object message, final ByteBuf out) throws IllegalStateException {
|
||||
|
||||
RntbdContextRequest request = (RntbdContextRequest)message;
|
||||
final RntbdContextRequest request = (RntbdContextRequest)message;
|
||||
out.markWriterIndex();
|
||||
|
||||
try {
|
||||
request.encode(out);
|
||||
} catch (IllegalStateException error) {
|
||||
} catch (final IllegalStateException error) {
|
||||
out.resetWriterIndex();
|
||||
throw error;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.microsoft.azure.cosmosdb.internal.UserAgentContainer;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.StoreResponse;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.RntbdTransportClient.Options;
|
||||
|
||||
public interface RntbdEndpoint extends AutoCloseable {
|
||||
|
||||
String getName();
|
||||
|
||||
@Override
|
||||
void close() throws RuntimeException;
|
||||
|
||||
CompletableFuture<StoreResponse> request(RntbdRequestArgs requestArgs);
|
||||
|
||||
interface Provider extends AutoCloseable {
|
||||
|
||||
@Override
|
||||
void close() throws RuntimeException;
|
||||
|
||||
Config config();
|
||||
|
||||
int count();
|
||||
|
||||
RntbdEndpoint get(URI physicalAddress);
|
||||
|
||||
Stream<RntbdEndpoint> list();
|
||||
}
|
||||
|
||||
final class Config {
|
||||
|
||||
private final Options options;
|
||||
private final SslContext sslContext;
|
||||
private final LogLevel wireLogLevel;
|
||||
|
||||
public Config(final Options options, final SslContext sslContext, final LogLevel wireLogLevel) {
|
||||
|
||||
checkNotNull(options, "options");
|
||||
checkNotNull(sslContext, "sslContext");
|
||||
|
||||
this.options = options;
|
||||
this.sslContext = sslContext;
|
||||
this.wireLogLevel = wireLogLevel;
|
||||
}
|
||||
|
||||
public int getConnectionTimeout() {
|
||||
final long value = this.options.getConnectionTimeout().toMillis();
|
||||
assert value <= Integer.MAX_VALUE;
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
public int getMaxChannelsPerEndpoint() {
|
||||
return this.options.getMaxChannelsPerEndpoint();
|
||||
}
|
||||
|
||||
public int getMaxRequestsPerChannel() {
|
||||
return this.options.getMaxRequestsPerChannel();
|
||||
}
|
||||
|
||||
public long getReceiveHangDetectionTime() {
|
||||
return this.options.getReceiveHangDetectionTime().toNanos();
|
||||
}
|
||||
|
||||
public long getRequestTimeout() {
|
||||
return this.options.getRequestTimeout().toNanos();
|
||||
}
|
||||
|
||||
public long getSendHangDetectionTime() {
|
||||
return this.options.getSendHangDetectionTime().toNanos();
|
||||
}
|
||||
|
||||
public SslContext getSslContext() {
|
||||
return this.sslContext;
|
||||
}
|
||||
|
||||
public UserAgentContainer getUserAgent() {
|
||||
return this.options.getUserAgent();
|
||||
}
|
||||
|
||||
public LogLevel getWireLogLevel() {
|
||||
return this.wireLogLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return RntbdObjectMapper.toJson(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,37 +27,61 @@ package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
|||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
||||
import java.util.Objects;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
final class RntbdFramer {
|
||||
|
||||
private RntbdFramer() {
|
||||
}
|
||||
|
||||
static boolean canDecodePayload(ByteBuf in) {
|
||||
return canDecodePayload(in, in.readerIndex());
|
||||
static boolean canDecodeHead(final ByteBuf in) throws CorruptedFrameException {
|
||||
|
||||
checkNotNull(in, "in");
|
||||
|
||||
if (in.readableBytes() < RntbdResponseStatus.LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int start = in.readerIndex();
|
||||
final long length = in.getUnsignedIntLE(start);
|
||||
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
final String reason = String.format("Head frame length exceeds Integer.MAX_VALUE, %d: %d",
|
||||
Integer.MAX_VALUE, length
|
||||
);
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
if (length < Integer.BYTES) {
|
||||
final String reason = String.format("Head frame length is less than size of length field, %d: %d",
|
||||
Integer.BYTES, length
|
||||
);
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
return length <= in.readableBytes();
|
||||
}
|
||||
|
||||
static boolean canDecodePayload(ByteBuf in, int start) {
|
||||
static boolean canDecodePayload(final ByteBuf in, final int start) {
|
||||
|
||||
Objects.requireNonNull(in);
|
||||
checkNotNull(in, "in");
|
||||
|
||||
int readerIndex = in.readerIndex();
|
||||
final int readerIndex = in.readerIndex();
|
||||
|
||||
if (start < readerIndex) {
|
||||
throw new IllegalArgumentException("start < in.readerIndex()");
|
||||
}
|
||||
|
||||
int offset = start - readerIndex;
|
||||
final int offset = start - readerIndex;
|
||||
|
||||
if (in.readableBytes() - offset < Integer.BYTES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long length = in.getUnsignedIntLE(start);
|
||||
final long length = in.getUnsignedIntLE(start);
|
||||
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
String reason = String.format("Payload frame length exceeds Integer.MAX_VALUE, %d: %d",
|
||||
final String reason = String.format("Payload frame length exceeds Integer.MAX_VALUE, %d: %d",
|
||||
Integer.MAX_VALUE, length
|
||||
);
|
||||
throw new CorruptedFrameException(reason);
|
||||
|
@ -66,31 +90,7 @@ final class RntbdFramer {
|
|||
return offset + Integer.BYTES + length <= in.readableBytes();
|
||||
}
|
||||
|
||||
static boolean canDecodeHead(ByteBuf in) throws CorruptedFrameException {
|
||||
|
||||
Objects.requireNonNull(in);
|
||||
|
||||
if (in.readableBytes() < RntbdResponseStatus.LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int start = in.readerIndex();
|
||||
long length = in.getUnsignedIntLE(start);
|
||||
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
String reason = String.format("Head frame length exceeds Integer.MAX_VALUE, %d: %d",
|
||||
Integer.MAX_VALUE, length
|
||||
);
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
if (length < Integer.BYTES) {
|
||||
String reason = String.format("Head frame length is less than size of length field, %d: %d",
|
||||
Integer.BYTES, length
|
||||
);
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
return length <= in.readableBytes();
|
||||
static boolean canDecodePayload(final ByteBuf in) {
|
||||
return canDecodePayload(in, in.readerIndex());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.codahale.metrics.Gauge;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricFilter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.RatioGauge;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.google.common.base.Stopwatch;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@JsonPropertyOrder({
|
||||
"lifetime", "requests", "responses", "errorResponses", "responseRate", "completionRate", "throughput"
|
||||
})
|
||||
public final class RntbdMetrics implements AutoCloseable {
|
||||
|
||||
// region Fields
|
||||
|
||||
private static final MetricRegistry registry = new MetricRegistry();
|
||||
|
||||
private final Gauge<Double> completionRate;
|
||||
private final Meter errorResponses;
|
||||
private final Stopwatch lifetime;
|
||||
private final String prefix;
|
||||
private final Meter requests;
|
||||
private final Gauge<Double> responseRate;
|
||||
private final Meter responses;
|
||||
|
||||
// endregion
|
||||
|
||||
// region Constructors
|
||||
|
||||
public RntbdMetrics(final String name) {
|
||||
|
||||
this.lifetime = Stopwatch.createStarted();
|
||||
this.prefix = name + '.';
|
||||
|
||||
this.requests = registry.register(this.prefix + "requests", new Meter());
|
||||
this.responses = registry.register(this.prefix + "responses", new Meter());
|
||||
this.errorResponses = registry.register(this.prefix + "errorResponses", new Meter());
|
||||
this.responseRate = registry.register(this.prefix + "responseRate", new ResponseRate(this));
|
||||
this.completionRate = registry.register(this.prefix + "completionRate", new CompletionRate(this));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Accessors
|
||||
|
||||
public double getCompletionRate() {
|
||||
return this.completionRate.getValue();
|
||||
}
|
||||
|
||||
public long getErrorResponses() {
|
||||
return this.errorResponses.getCount();
|
||||
}
|
||||
|
||||
public double getLifetime() {
|
||||
final Duration elapsed = this.lifetime.elapsed();
|
||||
return elapsed.getSeconds() + (1E-9D * elapsed.getNano());
|
||||
}
|
||||
|
||||
public long getRequests() {
|
||||
return this.requests.getCount();
|
||||
}
|
||||
|
||||
public double getResponseRate() {
|
||||
return this.responseRate.getValue();
|
||||
}
|
||||
|
||||
public long getResponses() {
|
||||
return this.responses.getCount();
|
||||
}
|
||||
|
||||
public double getThroughput() {
|
||||
return this.responses.getMeanRate();
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Methods
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
registry.removeMatching(MetricFilter.startsWith(this.prefix));
|
||||
}
|
||||
|
||||
public final void incrementErrorResponseCount() {
|
||||
this.errorResponses.mark();
|
||||
}
|
||||
|
||||
public final void incrementRequestCount() {
|
||||
this.requests.mark();
|
||||
}
|
||||
|
||||
public final void incrementResponseCount() {
|
||||
this.responses.mark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return RntbdObjectMapper.toJson(this);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private static final class CompletionRate extends RatioGauge {
|
||||
|
||||
private final RntbdMetrics metrics;
|
||||
|
||||
private CompletionRate(RntbdMetrics metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Ratio getRatio() {
|
||||
return Ratio.of(this.metrics.responses.getCount() - this.metrics.errorResponses.getCount(),
|
||||
this.metrics.requests.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResponseRate extends RatioGauge {
|
||||
|
||||
private final RntbdMetrics metrics;
|
||||
|
||||
private ResponseRate(RntbdMetrics metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Ratio getRatio() {
|
||||
return Ratio.of(this.metrics.responses.getCount(), this.metrics.requests.getCount());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,60 +24,83 @@
|
|||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.ser.PropertyFilter;
|
||||
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.EncoderException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
class RntbdObjectMapper {
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
private static final SimpleFilterProvider propertyFilterProvider = new SimpleFilterProvider();
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper().setFilterProvider(propertyFilterProvider);
|
||||
private static volatile ObjectWriter objectWriter = null;
|
||||
public final class RntbdObjectMapper {
|
||||
|
||||
private static final SimpleFilterProvider filterProvider;
|
||||
private static final ObjectMapper objectMapper;
|
||||
private static final ObjectWriter objectWriter;
|
||||
|
||||
static {
|
||||
objectMapper = new ObjectMapper().setFilterProvider(filterProvider = new SimpleFilterProvider());
|
||||
objectWriter = objectMapper.writer();
|
||||
}
|
||||
|
||||
private RntbdObjectMapper() {
|
||||
}
|
||||
|
||||
static JsonNode readTree(ByteBuf in) {
|
||||
|
||||
Objects.requireNonNull(in, "in");
|
||||
InputStream istream = new ByteBufInputStream(in);
|
||||
|
||||
try {
|
||||
return objectMapper.readTree(istream);
|
||||
} catch (IOException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
static ObjectNode readTree(final RntbdResponse response) {
|
||||
checkNotNull(response, "response");
|
||||
return readTree(response.getContent());
|
||||
}
|
||||
|
||||
static void registerPropertyFilter(Class<?> type, Class<? extends PropertyFilter> filter) {
|
||||
static ObjectNode readTree(final ByteBuf in) {
|
||||
|
||||
Objects.requireNonNull(type, "type");
|
||||
Objects.requireNonNull(filter, "filter");
|
||||
checkNotNull(in, "in");
|
||||
final JsonNode node;
|
||||
|
||||
try (final InputStream istream = new ByteBufInputStream(in)) {
|
||||
node = objectMapper.readTree(istream);
|
||||
} catch (final IOException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
|
||||
if (node.isObject()) {
|
||||
return (ObjectNode)node;
|
||||
}
|
||||
|
||||
final String cause = String.format("Expected %s, not %s", JsonNodeType.OBJECT, node.getNodeType());
|
||||
throw new CorruptedFrameException(cause);
|
||||
}
|
||||
|
||||
static void registerPropertyFilter(final Class<?> type, final Class<? extends PropertyFilter> filter) {
|
||||
|
||||
checkNotNull(type, "type");
|
||||
checkNotNull(filter, "filter");
|
||||
|
||||
try {
|
||||
propertyFilterProvider.addFilter(type.getSimpleName(), filter.newInstance());
|
||||
} catch (ReflectiveOperationException error) {
|
||||
filterProvider.addFilter(type.getSimpleName(), filter.newInstance());
|
||||
} catch (final ReflectiveOperationException error) {
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
}
|
||||
|
||||
static ObjectWriter writer() {
|
||||
if (objectWriter == null) {
|
||||
synchronized (objectMapper) {
|
||||
if (objectWriter == null) {
|
||||
objectWriter = objectMapper.writer();
|
||||
}
|
||||
}
|
||||
public static String toJson(Object value) {
|
||||
try {
|
||||
return objectWriter.writeValueAsString(value);
|
||||
} catch (final JsonProcessingException error) {
|
||||
throw new EncoderException(error);
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectWriter writer() {
|
||||
return objectWriter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.helpers.FormattingTuple;
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public final class RntbdReporter {
|
||||
|
||||
private static final String codeSource = Paths.get(
|
||||
RntbdReporter.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getFileName().toString();
|
||||
|
||||
private RntbdReporter() {
|
||||
}
|
||||
|
||||
public static void reportIssue(Logger logger, Object subject, String format, Object... arguments) {
|
||||
if (logger.isErrorEnabled()) {
|
||||
doReportIssue(logger, subject, format, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
public static void reportIssueUnless(
|
||||
boolean predicate, Logger logger, Object subject, String format, Object... arguments
|
||||
) {
|
||||
if (!predicate && logger.isErrorEnabled()) {
|
||||
doReportIssue(logger, subject, format, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
private static void doReportIssue(Logger logger, Object subject, String format, Object[] arguments) {
|
||||
|
||||
FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
|
||||
StackTraceElement[] stackTraceElements = new Exception().getStackTrace();
|
||||
Throwable throwable = formattingTuple.getThrowable();
|
||||
|
||||
if (throwable == null) {
|
||||
logger.error("Report this {} issue to ensure it is addressed:\n[{}]\n[{}]\n[{}]",
|
||||
codeSource, subject, stackTraceElements[2], formattingTuple.getMessage()
|
||||
);
|
||||
} else {
|
||||
logger.error("Report this {} issue to ensure it is addressed:\n[{}]\n[{}]\n[{}{}{}]",
|
||||
codeSource, subject, stackTraceElements[2], formattingTuple.getMessage(),
|
||||
throwable, ExceptionUtils.getStackTrace(throwable)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,79 +27,66 @@ package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
|||
import com.microsoft.azure.cosmosdb.rx.internal.RxDocumentServiceRequest;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
final public class RntbdRequest {
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
final private static byte[] EmptyByteArray = {};
|
||||
public final class RntbdRequest {
|
||||
|
||||
final private RntbdRequestFrame frame;
|
||||
final private RntbdRequestHeaders headers;
|
||||
final private byte[] payload;
|
||||
private static final byte[] EmptyByteArray = {};
|
||||
|
||||
private RntbdRequest(RntbdRequestFrame frame, RntbdRequestHeaders headers, byte[] payload) {
|
||||
private final RntbdRequestFrame frame;
|
||||
private final RntbdRequestHeaders headers;
|
||||
private final byte[] payload;
|
||||
|
||||
Objects.requireNonNull(frame, "frame");
|
||||
Objects.requireNonNull(headers, "headers");
|
||||
private RntbdRequest(final RntbdRequestFrame frame, final RntbdRequestHeaders headers, final byte[] payload) {
|
||||
|
||||
checkNotNull(frame, "frame");
|
||||
checkNotNull(headers, "headers");
|
||||
|
||||
this.frame = frame;
|
||||
this.headers = headers;
|
||||
this.payload = payload == null ? EmptyByteArray : payload;
|
||||
}
|
||||
|
||||
static RntbdRequest decode(ByteBuf in) {
|
||||
public UUID getActivityId() {
|
||||
return this.frame.getActivityId();
|
||||
}
|
||||
|
||||
int resourceOperationCode = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
static RntbdRequest decode(final ByteBuf in) {
|
||||
|
||||
final int resourceOperationCode = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
|
||||
if (resourceOperationCode == 0) {
|
||||
String reason = String.format("resourceOperationCode=0x%08X", resourceOperationCode);
|
||||
final String reason = String.format("resourceOperationCode=0x%08X", resourceOperationCode);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
int start = in.readerIndex();
|
||||
int expectedLength = in.readIntLE();
|
||||
final int start = in.readerIndex();
|
||||
final int expectedLength = in.readIntLE();
|
||||
|
||||
RntbdRequestFrame header = RntbdRequestFrame.decode(in);
|
||||
RntbdRequestHeaders metadata = RntbdRequestHeaders.decode(in);
|
||||
ByteBuf payloadBuf = in.readSlice(expectedLength - (in.readerIndex() - start));
|
||||
final RntbdRequestFrame header = RntbdRequestFrame.decode(in);
|
||||
final RntbdRequestHeaders metadata = RntbdRequestHeaders.decode(in);
|
||||
final ByteBuf payloadBuf = in.readSlice(expectedLength - (in.readerIndex() - start));
|
||||
|
||||
int observedLength = in.readerIndex() - start;
|
||||
final int observedLength = in.readerIndex() - start;
|
||||
|
||||
if (observedLength != expectedLength) {
|
||||
String reason = String.format("expectedLength=%d, observedLength=%d", expectedLength, observedLength);
|
||||
final String reason = String.format("expectedLength=%d, observedLength=%d", expectedLength, observedLength);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
byte[] payload = new byte[payloadBuf.readableBytes()];
|
||||
final byte[] payload = new byte[payloadBuf.readableBytes()];
|
||||
payloadBuf.readBytes(payload);
|
||||
in.discardReadBytes();
|
||||
|
||||
return new RntbdRequest(header, metadata, payload);
|
||||
}
|
||||
|
||||
public static RntbdRequest from(RntbdRequestArgs args) {
|
||||
void encode(final ByteBuf out) {
|
||||
|
||||
RxDocumentServiceRequest serviceRequest = args.getServiceRequest();
|
||||
|
||||
final RntbdRequestFrame frame = new RntbdRequestFrame(
|
||||
serviceRequest.getActivityId(),
|
||||
serviceRequest.getOperationType(),
|
||||
serviceRequest.getResourceType());
|
||||
|
||||
final RntbdRequestHeaders headers = new RntbdRequestHeaders(args, frame);
|
||||
|
||||
return new RntbdRequest(frame, headers, serviceRequest.getContent());
|
||||
}
|
||||
|
||||
public UUID getActivityId() {
|
||||
return this.frame.getActivityId();
|
||||
}
|
||||
|
||||
void encode(ByteBuf out) {
|
||||
|
||||
int expectedLength = RntbdRequestFrame.LENGTH + headers.computeLength();
|
||||
int start = out.readerIndex();
|
||||
final int expectedLength = RntbdRequestFrame.LENGTH + this.headers.computeLength();
|
||||
final int start = out.readerIndex();
|
||||
|
||||
out.writeIntLE(expectedLength);
|
||||
this.frame.encode(out);
|
||||
|
@ -112,4 +99,18 @@ final public class RntbdRequest {
|
|||
out.writeBytes(this.payload);
|
||||
}
|
||||
}
|
||||
|
||||
public static RntbdRequest from(final RntbdRequestArgs args) {
|
||||
|
||||
final RxDocumentServiceRequest serviceRequest = args.getServiceRequest();
|
||||
|
||||
final RntbdRequestFrame frame = new RntbdRequestFrame(
|
||||
args.getActivityId(),
|
||||
serviceRequest.getOperationType(),
|
||||
serviceRequest.getResourceType());
|
||||
|
||||
final RntbdRequestHeaders headers = new RntbdRequestHeaders(args, frame);
|
||||
|
||||
return new RntbdRequest(frame, headers, serviceRequest.getContent());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,26 +31,31 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.slf4j.Logger;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
final public class RntbdRequestArgs {
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public final class RntbdRequestArgs {
|
||||
|
||||
private static final String className = RntbdRequestArgs.class.getCanonicalName();
|
||||
|
||||
private final UUID activityId;
|
||||
private final long birthTime;
|
||||
private final Stopwatch lifetime;
|
||||
private final URI physicalAddress;
|
||||
private final String replicaPath;
|
||||
private final RxDocumentServiceRequest serviceRequest;
|
||||
|
||||
|
||||
public RntbdRequestArgs(RxDocumentServiceRequest serviceRequest, String replicaPath) {
|
||||
public RntbdRequestArgs(final RxDocumentServiceRequest serviceRequest, final URI physicalAddress) {
|
||||
this.activityId = UUID.fromString(serviceRequest.getActivityId());
|
||||
this.birthTime = System.nanoTime();
|
||||
this.lifetime = Stopwatch.createStarted();
|
||||
this.replicaPath = StringUtils.stripEnd(replicaPath, "/");
|
||||
this.physicalAddress = physicalAddress;
|
||||
this.replicaPath = StringUtils.stripEnd(physicalAddress.getPath(), "/");
|
||||
this.serviceRequest = serviceRequest;
|
||||
}
|
||||
|
||||
|
@ -70,25 +75,32 @@ final public class RntbdRequestArgs {
|
|||
return this.replicaPath;
|
||||
}
|
||||
|
||||
RxDocumentServiceRequest getServiceRequest() {
|
||||
public URI getPhysicalAddress() {
|
||||
return this.physicalAddress;
|
||||
}
|
||||
|
||||
public RxDocumentServiceRequest getServiceRequest() {
|
||||
return this.serviceRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[activityId: " + this.serviceRequest.getActivityId() + ", operationType: "
|
||||
+ this.serviceRequest.getOperationType() + ", resourceType: "
|
||||
+ this.serviceRequest.getResourceType() + ", replicaPath: "
|
||||
+ this.replicaPath + "]";
|
||||
return '[' + RntbdRequestArgs.className + '(' + this.serviceRequest.getActivityId()
|
||||
+ ", origin: " + this.physicalAddress.getScheme() + "://" + this.physicalAddress.getAuthority()
|
||||
+ ", operationType: " + this.serviceRequest.getOperationType()
|
||||
+ ", resourceType: " + this.serviceRequest.getResourceType()
|
||||
+ ", replicaPath: " + this.replicaPath
|
||||
+ ", lifetime: " + this.getLifetime()
|
||||
+ ")]";
|
||||
}
|
||||
|
||||
public void traceOperation(Logger logger, ChannelHandlerContext context, String operationName, Object... args) {
|
||||
public void traceOperation(final Logger logger, final ChannelHandlerContext context, final String operationName, final Object... args) {
|
||||
|
||||
Objects.requireNonNull(logger);
|
||||
checkNotNull(logger, "logger");
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
final BigDecimal lifetime = BigDecimal.valueOf(this.lifetime.elapsed().toNanos(), 6);
|
||||
logger.info("{},{},\"{}({})\",\"{}\",\"{}\"", this.birthTime, lifetime, operationName,
|
||||
logger.trace("{},{},\"{}({})\",\"{}\",\"{}\"", this.birthTime, lifetime, operationName,
|
||||
Stream.of(args).map(arg ->
|
||||
arg == null ? "null" : arg.toString()).collect(Collectors.joining(",")
|
||||
),
|
||||
|
|
|
@ -30,7 +30,7 @@ import io.netty.handler.codec.ByteToMessageDecoder;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
final public class RntbdRequestDecoder extends ByteToMessageDecoder {
|
||||
public final class RntbdRequestDecoder extends ByteToMessageDecoder {
|
||||
/**
|
||||
* Prepare for decoding an @{link RntbdRequest} or fire a channel readTree event to pass the input message along
|
||||
*
|
||||
|
@ -39,12 +39,12 @@ final public class RntbdRequestDecoder extends ByteToMessageDecoder {
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext context, Object message) throws Exception {
|
||||
public void channelRead(final ChannelHandlerContext context, final Object message) throws Exception {
|
||||
|
||||
if (message instanceof ByteBuf) {
|
||||
|
||||
ByteBuf in = (ByteBuf)message;
|
||||
int resourceOperationType = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
final ByteBuf in = (ByteBuf)message;
|
||||
final int resourceOperationType = in.getInt(in.readerIndex() + Integer.BYTES);
|
||||
|
||||
if (resourceOperationType != 0) {
|
||||
super.channelRead(context, message);
|
||||
|
@ -67,14 +67,14 @@ final public class RntbdRequestDecoder extends ByteToMessageDecoder {
|
|||
* @throws IllegalStateException thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) throws IllegalStateException {
|
||||
protected void decode(final ChannelHandlerContext context, final ByteBuf in, final List<Object> out) throws IllegalStateException {
|
||||
|
||||
RntbdRequest request;
|
||||
final RntbdRequest request;
|
||||
in.markReaderIndex();
|
||||
|
||||
try {
|
||||
request = RntbdRequest.decode(in);
|
||||
} catch (IllegalStateException error) {
|
||||
} catch (final IllegalStateException error) {
|
||||
in.resetReaderIndex();
|
||||
throw error;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import io.netty.handler.codec.MessageToByteEncoder;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
final public class RntbdRequestEncoder extends MessageToByteEncoder {
|
||||
public final class RntbdRequestEncoder extends MessageToByteEncoder {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdRequestEncoder.class);
|
||||
|
||||
|
@ -44,7 +44,7 @@ final public class RntbdRequestEncoder extends MessageToByteEncoder {
|
|||
* @return {@code true}, if the given message is an an {@link RntbdRequest} instance; otherwise @{false}
|
||||
*/
|
||||
@Override
|
||||
public boolean acceptOutboundMessage(Object message) {
|
||||
public boolean acceptOutboundMessage(final Object message) {
|
||||
return message instanceof RntbdRequestArgs;
|
||||
}
|
||||
|
||||
|
@ -58,20 +58,20 @@ final public class RntbdRequestEncoder extends MessageToByteEncoder {
|
|||
* @param out the {@link ByteBuf} into which the encoded message will be written
|
||||
*/
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext context, Object message, ByteBuf out) throws Exception {
|
||||
protected void encode(final ChannelHandlerContext context, final Object message, final ByteBuf out) throws Exception {
|
||||
|
||||
RntbdRequest request = RntbdRequest.from((RntbdRequestArgs)message);
|
||||
int start = out.writerIndex();
|
||||
final RntbdRequest request = RntbdRequest.from((RntbdRequestArgs)message);
|
||||
final int start = out.writerIndex();
|
||||
|
||||
try {
|
||||
request.encode(out);
|
||||
} catch (Throwable error) {
|
||||
} catch (final Throwable error) {
|
||||
out.writerIndex(start);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
int length = out.writerIndex() - start;
|
||||
final int length = out.writerIndex() - start;
|
||||
logger.debug("{}: ENCODE COMPLETE: length={}, request={}", context.channel(), length, request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,22 +38,22 @@ final class RntbdRequestFrame {
|
|||
|
||||
// region Fields
|
||||
|
||||
final static int LENGTH = Integer.BYTES // messageLength
|
||||
static final int LENGTH = Integer.BYTES // messageLength
|
||||
+ Short.BYTES // resourceType
|
||||
+ Short.BYTES // operationType
|
||||
+ 2 * Long.BYTES; // activityId
|
||||
|
||||
final private UUID activityId;
|
||||
final private RntbdOperationType operationType;
|
||||
final private RntbdResourceType resourceType;
|
||||
private final UUID activityId;
|
||||
private final RntbdOperationType operationType;
|
||||
private final RntbdResourceType resourceType;
|
||||
|
||||
// region Constructors
|
||||
|
||||
RntbdRequestFrame(String activityId, OperationType operationType, ResourceType resourceType) {
|
||||
this(UUID.fromString(activityId), map(operationType), map(resourceType));
|
||||
RntbdRequestFrame(final UUID activityId, final OperationType operationType, final ResourceType resourceType) {
|
||||
this(activityId, map(operationType), map(resourceType));
|
||||
}
|
||||
|
||||
RntbdRequestFrame(UUID activityId, RntbdOperationType operationType, RntbdResourceType resourceType) {
|
||||
RntbdRequestFrame(final UUID activityId, final RntbdOperationType operationType, final RntbdResourceType resourceType) {
|
||||
this.activityId = activityId;
|
||||
this.operationType = operationType;
|
||||
this.resourceType = resourceType;
|
||||
|
@ -63,16 +63,34 @@ final class RntbdRequestFrame {
|
|||
|
||||
// region Methods
|
||||
|
||||
static RntbdRequestFrame decode(ByteBuf in) {
|
||||
UUID getActivityId() {
|
||||
return this.activityId;
|
||||
}
|
||||
|
||||
RntbdResourceType resourceType = RntbdResourceType.fromId(in.readShortLE());
|
||||
RntbdOperationType operationType = RntbdOperationType.fromId(in.readShortLE());
|
||||
UUID activityId = RntbdUUID.decode(in);
|
||||
RntbdOperationType getOperationType() {
|
||||
return this.operationType;
|
||||
}
|
||||
|
||||
RntbdResourceType getResourceType() {
|
||||
return this.resourceType;
|
||||
}
|
||||
|
||||
static RntbdRequestFrame decode(final ByteBuf in) {
|
||||
|
||||
final RntbdResourceType resourceType = RntbdResourceType.fromId(in.readShortLE());
|
||||
final RntbdOperationType operationType = RntbdOperationType.fromId(in.readShortLE());
|
||||
final UUID activityId = RntbdUUID.decode(in);
|
||||
|
||||
return new RntbdRequestFrame(activityId, operationType, resourceType);
|
||||
}
|
||||
|
||||
private static RntbdResourceType map(ResourceType resourceType) {
|
||||
void encode(final ByteBuf out) {
|
||||
out.writeShortLE(this.resourceType.id());
|
||||
out.writeShortLE(this.operationType.id());
|
||||
RntbdUUID.encode(this.activityId, out);
|
||||
}
|
||||
|
||||
private static RntbdResourceType map(final ResourceType resourceType) {
|
||||
|
||||
switch (resourceType) {
|
||||
case Attachment:
|
||||
|
@ -132,12 +150,12 @@ final class RntbdRequestFrame {
|
|||
case RidRange:
|
||||
return RntbdResourceType.RidRange;
|
||||
default:
|
||||
String reason = String.format(Locale.ROOT, "Unrecognized resource type: %s", resourceType);
|
||||
final String reason = String.format(Locale.ROOT, "Unrecognized resource type: %s", resourceType);
|
||||
throw new UnsupportedOperationException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
private static RntbdOperationType map(OperationType operationType) {
|
||||
private static RntbdOperationType map(final OperationType operationType) {
|
||||
|
||||
switch (operationType) {
|
||||
case Crash:
|
||||
|
@ -207,28 +225,10 @@ final class RntbdRequestFrame {
|
|||
case AddComputeGatewayRequestCharges:
|
||||
return RntbdOperationType.AddComputeGatewayRequestCharges;
|
||||
default:
|
||||
String reason = String.format(Locale.ROOT, "Unrecognized operation type: %s", operationType);
|
||||
final String reason = String.format(Locale.ROOT, "Unrecognized operation type: %s", operationType);
|
||||
throw new UnsupportedOperationException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
UUID getActivityId() {
|
||||
return activityId;
|
||||
}
|
||||
|
||||
RntbdOperationType getOperationType() {
|
||||
return operationType;
|
||||
}
|
||||
|
||||
RntbdResourceType getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
void encode(ByteBuf out) {
|
||||
out.writeShortLE(this.resourceType.id());
|
||||
out.writeShortLE(this.operationType.id());
|
||||
RntbdUUID.encode(this.activityId, out);
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
|||
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
final public class RntbdRequestFramer extends LengthFieldBasedFrameDecoder {
|
||||
public final class RntbdRequestFramer extends LengthFieldBasedFrameDecoder {
|
||||
|
||||
public RntbdRequestFramer() {
|
||||
super(ByteOrder.LITTLE_ENDIAN, Integer.MAX_VALUE, 0, Integer.BYTES, -Integer.BYTES, 0, true);
|
||||
|
|
|
@ -41,15 +41,13 @@ import com.microsoft.azure.cosmosdb.rx.internal.RxDocumentServiceRequest;
|
|||
import io.netty.buffer.ByteBuf;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.validator.routines.DoubleValidator;
|
||||
import org.apache.commons.validator.routines.LongValidator;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.microsoft.azure.cosmosdb.internal.HttpConstants.HttpHeaders;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.WFConstants.BackendHeaders;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.RntbdConsistencyLevel;
|
||||
|
@ -69,22 +67,20 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
// region Fields
|
||||
|
||||
private static final String UrlTrim = "/+";
|
||||
private static final DoubleValidator doubleValidator = DoubleValidator.getInstance();
|
||||
private static final LongValidator longValidator = LongValidator.getInstance();
|
||||
|
||||
// endregion
|
||||
|
||||
// region Constructors
|
||||
|
||||
RntbdRequestHeaders(RntbdRequestArgs args, RntbdRequestFrame frame) {
|
||||
RntbdRequestHeaders(final RntbdRequestArgs args, final RntbdRequestFrame frame) {
|
||||
|
||||
this();
|
||||
|
||||
Objects.requireNonNull(args, "args");
|
||||
Objects.requireNonNull(frame, "frame");
|
||||
checkNotNull(args, "args");
|
||||
checkNotNull(frame, "frame");
|
||||
|
||||
RxDocumentServiceRequest request = args.getServiceRequest();
|
||||
byte[] content = request.getContent();
|
||||
final RxDocumentServiceRequest request = args.getServiceRequest();
|
||||
final byte[] content = request.getContent();
|
||||
|
||||
this.getPayloadPresent().setValue(content != null && content.length > 0);
|
||||
this.getReplicaPath().setValue(args.getReplicaPath());
|
||||
|
@ -188,8 +184,8 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
|
||||
// region Methods
|
||||
|
||||
static RntbdRequestHeaders decode(ByteBuf in) {
|
||||
RntbdRequestHeaders metadata = new RntbdRequestHeaders();
|
||||
static RntbdRequestHeaders decode(final ByteBuf in) {
|
||||
final RntbdRequestHeaders metadata = new RntbdRequestHeaders();
|
||||
return RntbdRequestHeaders.decode(in, metadata);
|
||||
}
|
||||
|
||||
|
@ -581,67 +577,67 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
return this.get(RntbdRequestHeader.UserName);
|
||||
}
|
||||
|
||||
private void addAimHeader(Map<String, String> headers) {
|
||||
private void addAimHeader(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.A_IM);
|
||||
final String value = headers.get(HttpHeaders.A_IM);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getAIM().setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAllowScanOnQuery(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.ENABLE_SCAN_IN_QUERY);
|
||||
private void addAllowScanOnQuery(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.ENABLE_SCAN_IN_QUERY);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getEnableScanInQuery().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addBinaryIdIfPresent(Map<String, String> headers) {
|
||||
String value = headers.get(BackendHeaders.BINARY_ID);
|
||||
private void addBinaryIdIfPresent(final Map<String, String> headers) {
|
||||
final String value = headers.get(BackendHeaders.BINARY_ID);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getBinaryId().setValue(Base64.getDecoder().decode(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addCanCharge(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.CAN_CHARGE);
|
||||
private void addCanCharge(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.CAN_CHARGE);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getCanCharge().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addCanOfferReplaceComplete(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.CAN_OFFER_REPLACE_COMPLETE);
|
||||
private void addCanOfferReplaceComplete(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.CAN_OFFER_REPLACE_COMPLETE);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getCanOfferReplaceComplete().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addCanThrottle(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.CAN_THROTTLE);
|
||||
private void addCanThrottle(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.CAN_THROTTLE);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getCanThrottle().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addCollectionRemoteStorageSecurityIdentifier(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.COLLECTION_REMOTE_STORAGE_SECURITY_IDENTIFIER);
|
||||
private void addCollectionRemoteStorageSecurityIdentifier(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.COLLECTION_REMOTE_STORAGE_SECURITY_IDENTIFIER);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getCollectionRemoteStorageSecurityIdentifier().setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void addConsistencyLevelHeader(Map<String, String> headers) {
|
||||
private void addConsistencyLevelHeader(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.CONSISTENCY_LEVEL);
|
||||
final String value = headers.get(HttpHeaders.CONSISTENCY_LEVEL);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
ConsistencyLevel level = EnumUtils.getEnumIgnoreCase(ConsistencyLevel.class, value);
|
||||
final ConsistencyLevel level = EnumUtils.getEnumIgnoreCase(ConsistencyLevel.class, value);
|
||||
|
||||
if (level == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.CONSISTENCY_LEVEL,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -670,16 +666,16 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addContentSerializationFormat(Map<String, String> headers) {
|
||||
private void addContentSerializationFormat(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.CONTENT_SERIALIZATION_FORMAT);
|
||||
final String value = headers.get(HttpHeaders.CONTENT_SERIALIZATION_FORMAT);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
ContentSerializationFormat format = EnumUtils.getEnumIgnoreCase(ContentSerializationFormat.class, value);
|
||||
final ContentSerializationFormat format = EnumUtils.getEnumIgnoreCase(ContentSerializationFormat.class, value);
|
||||
|
||||
if (format == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.CONTENT_SERIALIZATION_FORMAT,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -698,14 +694,14 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addContinuationToken(RxDocumentServiceRequest request) {
|
||||
String value = request.getContinuation();
|
||||
private void addContinuationToken(final RxDocumentServiceRequest request) {
|
||||
final String value = request.getContinuation();
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getContinuationToken().setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDateHeader(Map<String, String> headers) {
|
||||
private void addDateHeader(final Map<String, String> headers) {
|
||||
|
||||
// Since the HTTP date header is overridden by some proxies/http client libraries, we support an additional date
|
||||
// header and prefer that to the (regular) date header
|
||||
|
@ -721,51 +717,51 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addDisableRUPerMinuteUsage(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.DISABLE_RU_PER_MINUTE_USAGE);
|
||||
private void addDisableRUPerMinuteUsage(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.DISABLE_RU_PER_MINUTE_USAGE);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getDisableRUPerMinuteUsage().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addEmitVerboseTracesInQuery(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.EMIT_VERBOSE_TRACES_IN_QUERY);
|
||||
private void addEmitVerboseTracesInQuery(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.EMIT_VERBOSE_TRACES_IN_QUERY);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getEmitVerboseTracesInQuery().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addEnableLogging(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.ENABLE_LOGGING);
|
||||
private void addEnableLogging(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.ENABLE_LOGGING);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getEnableLogging().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addEnableLowPrecisionOrderBy(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.ENABLE_LOW_PRECISION_ORDER_BY);
|
||||
private void addEnableLowPrecisionOrderBy(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.ENABLE_LOW_PRECISION_ORDER_BY);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getEnableLowPrecisionOrderBy().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addEntityId(Map<String, String> headers) {
|
||||
String value = headers.get(BackendHeaders.ENTITY_ID);
|
||||
private void addEntityId(final Map<String, String> headers) {
|
||||
final String value = headers.get(BackendHeaders.ENTITY_ID);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getEntityId().setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEnumerationDirection(Map<String, String> headers) {
|
||||
private void addEnumerationDirection(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.ENUMERATION_DIRECTION);
|
||||
final String value = headers.get(HttpHeaders.ENUMERATION_DIRECTION);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
EnumerationDirection direction = EnumUtils.getEnumIgnoreCase(EnumerationDirection.class, value);
|
||||
final EnumerationDirection direction = EnumUtils.getEnumIgnoreCase(EnumerationDirection.class, value);
|
||||
|
||||
if (direction == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.ENUMERATION_DIRECTION,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -784,23 +780,23 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addExcludeSystemProperties(Map<String, String> headers) {
|
||||
String value = headers.get(BackendHeaders.EXCLUDE_SYSTEM_PROPERTIES);
|
||||
private void addExcludeSystemProperties(final Map<String, String> headers) {
|
||||
final String value = headers.get(BackendHeaders.EXCLUDE_SYSTEM_PROPERTIES);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getExcludeSystemProperties().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addFanoutOperationStateHeader(Map<String, String> headers) {
|
||||
private void addFanoutOperationStateHeader(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(BackendHeaders.FANOUT_OPERATION_STATE);
|
||||
final String value = headers.get(BackendHeaders.FANOUT_OPERATION_STATE);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
FanoutOperationState format = EnumUtils.getEnumIgnoreCase(FanoutOperationState.class, value);
|
||||
final FanoutOperationState format = EnumUtils.getEnumIgnoreCase(FanoutOperationState.class, value);
|
||||
|
||||
if (format == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
BackendHeaders.FANOUT_OPERATION_STATE,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -819,23 +815,23 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addIfModifiedSinceHeader(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.IF_MODIFIED_SINCE);
|
||||
private void addIfModifiedSinceHeader(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.IF_MODIFIED_SINCE);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getIfModifiedSince().setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void addIndexingDirectiveHeader(Map<String, String> headers) {
|
||||
private void addIndexingDirectiveHeader(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.INDEXING_DIRECTIVE);
|
||||
final String value = headers.get(HttpHeaders.INDEXING_DIRECTIVE);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
IndexingDirective directive = EnumUtils.getEnumIgnoreCase(IndexingDirective.class, value);
|
||||
final IndexingDirective directive = EnumUtils.getEnumIgnoreCase(IndexingDirective.class, value);
|
||||
|
||||
if (directive == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.INDEXING_DIRECTIVE,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -857,35 +853,35 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addIsAutoScaleRequest(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.IS_AUTO_SCALE_REQUEST);
|
||||
private void addIsAutoScaleRequest(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.IS_AUTO_SCALE_REQUEST);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getIsAutoScaleRequest().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addIsFanout(Map<String, String> headers) {
|
||||
String value = headers.get(BackendHeaders.IS_FANOUT_REQUEST);
|
||||
private void addIsFanout(final Map<String, String> headers) {
|
||||
final String value = headers.get(BackendHeaders.IS_FANOUT_REQUEST);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getIsFanout().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addIsReadOnlyScript(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.IS_READ_ONLY_SCRIPT);
|
||||
private void addIsReadOnlyScript(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.IS_READ_ONLY_SCRIPT);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getIsReadOnlyScript().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addIsUserRequest(Map<String, String> headers) {
|
||||
String value = headers.get(BackendHeaders.IS_USER_REQUEST);
|
||||
private void addIsUserRequest(final Map<String, String> headers) {
|
||||
final String value = headers.get(BackendHeaders.IS_USER_REQUEST);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getIsUserRequest().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addMatchHeader(Map<String, String> headers, RntbdOperationType operationType) {
|
||||
private void addMatchHeader(final Map<String, String> headers, final RntbdOperationType operationType) {
|
||||
|
||||
String match = null;
|
||||
|
||||
|
@ -904,16 +900,16 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addMigrateCollectionDirectiveHeader(Map<String, String> headers) {
|
||||
private void addMigrateCollectionDirectiveHeader(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.MIGRATE_COLLECTION_DIRECTIVE);
|
||||
final String value = headers.get(HttpHeaders.MIGRATE_COLLECTION_DIRECTIVE);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
MigrateCollectionDirective directive = EnumUtils.getEnumIgnoreCase(MigrateCollectionDirective.class, value);
|
||||
final MigrateCollectionDirective directive = EnumUtils.getEnumIgnoreCase(MigrateCollectionDirective.class, value);
|
||||
|
||||
if (directive == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.MIGRATE_COLLECTION_DIRECTIVE,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -933,77 +929,68 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addPageSize(Map<String, String> headers) {
|
||||
private void addPageSize(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.PAGE_SIZE);
|
||||
final String value = headers.get(HttpHeaders.PAGE_SIZE);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
final Long pageSize = longValidator.validate(value, Locale.ROOT);
|
||||
|
||||
if (pageSize == null || !longValidator.isInRange(pageSize, -1, 0xFFFFFFFFL)) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.PAGE_SIZE,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
this.getPageSize().setValue((int)(pageSize < 0 ? 0xFFFFFFFFL : pageSize));
|
||||
final long aLong = parseLong(HttpHeaders.PAGE_SIZE, value, -1, 0xFFFFFFFFL);
|
||||
this.getPageSize().setValue((int)(aLong < 0 ? 0xFFFFFFFFL : aLong));
|
||||
}
|
||||
}
|
||||
|
||||
private void addPopulateCollectionThroughputInfo(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.POPULATE_COLLECTION_THROUGHPUT_INFO);
|
||||
private void addPopulateCollectionThroughputInfo(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.POPULATE_COLLECTION_THROUGHPUT_INFO);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getPopulateCollectionThroughputInfo().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addPopulatePartitionStatistics(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.POPULATE_PARTITION_STATISTICS);
|
||||
private void addPopulatePartitionStatistics(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.POPULATE_PARTITION_STATISTICS);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getPopulatePartitionStatistics().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addPopulateQueryMetrics(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.POPULATE_QUERY_METRICS);
|
||||
private void addPopulateQueryMetrics(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.POPULATE_QUERY_METRICS);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getPopulateQueryMetrics().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addPopulateQuotaInfo(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.POPULATE_QUOTA_INFO);
|
||||
private void addPopulateQuotaInfo(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.POPULATE_QUOTA_INFO);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getPopulateQuotaInfo().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addProfileRequest(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.PROFILE_REQUEST);
|
||||
private void addProfileRequest(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.PROFILE_REQUEST);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getProfileRequest().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addQueryForceScan(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.FORCE_QUERY_SCAN);
|
||||
private void addQueryForceScan(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.FORCE_QUERY_SCAN);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getForceQueryScan().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addRemoteStorageType(Map<String, String> headers) {
|
||||
private void addRemoteStorageType(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(BackendHeaders.REMOTE_STORAGE_TYPE);
|
||||
final String value = headers.get(BackendHeaders.REMOTE_STORAGE_TYPE);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
RemoteStorageType type = EnumUtils.getEnumIgnoreCase(RemoteStorageType.class, value);
|
||||
final RemoteStorageType type = EnumUtils.getEnumIgnoreCase(RemoteStorageType.class, value);
|
||||
|
||||
if (type == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
BackendHeaders.REMOTE_STORAGE_TYPE,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -1022,9 +1009,9 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addResourceIdOrPathHeaders(RxDocumentServiceRequest request) {
|
||||
private void addResourceIdOrPathHeaders(final RxDocumentServiceRequest request) {
|
||||
|
||||
String value = request.getResourceId();
|
||||
final String value = request.getResourceId();
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
// Name-based can also have ResourceId because gateway might have generated it
|
||||
|
@ -1036,8 +1023,8 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
// Assumption: format is like "dbs/dbName/colls/collName/docs/docName" or "/dbs/dbName/colls/collName",
|
||||
// not "apps/appName/partitions/partitionKey/replicas/replicaId/dbs/dbName"
|
||||
|
||||
String address = request.getResourceAddress();
|
||||
String[] fragments = address.split(UrlTrim);
|
||||
final String address = request.getResourceAddress();
|
||||
final String[] fragments = address.split(UrlTrim);
|
||||
int count = fragments.length;
|
||||
int index = 0;
|
||||
|
||||
|
@ -1052,7 +1039,7 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
this.getDatabaseName().setValue(fragments[index + 1]);
|
||||
break;
|
||||
default:
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidResourceAddress,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidResourceAddress,
|
||||
value, address);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
@ -1111,41 +1098,33 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addResponseContinuationTokenLimitInKb(Map<String, String> headers) {
|
||||
private void addResponseContinuationTokenLimitInKb(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.RESPONSE_CONTINUATION_TOKEN_LIMIT_IN_KB);
|
||||
final String value = headers.get(HttpHeaders.RESPONSE_CONTINUATION_TOKEN_LIMIT_IN_KB);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
Long tokenLimit = longValidator.validate(value, Locale.ROOT);
|
||||
|
||||
if (tokenLimit == null || !longValidator.isInRange(tokenLimit, 0, 0xFFFFFFFFL)) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.RESPONSE_CONTINUATION_TOKEN_LIMIT_IN_KB,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
this.getResponseContinuationTokenLimitInKb().setValue((int)(tokenLimit < 0 ? 0xFFFFFFFFL : tokenLimit));
|
||||
final long aLong = parseLong(HttpHeaders.RESPONSE_CONTINUATION_TOKEN_LIMIT_IN_KB, value, 0, 0xFFFFFFFFL);
|
||||
this.getResponseContinuationTokenLimitInKb().setValue((int)(aLong < 0 ? 0xFFFFFFFFL : aLong));
|
||||
}
|
||||
}
|
||||
|
||||
private void addShareThroughput(Map<String, String> headers) {
|
||||
String value = headers.get(BackendHeaders.SHARE_THROUGHPUT);
|
||||
private void addShareThroughput(final Map<String, String> headers) {
|
||||
final String value = headers.get(BackendHeaders.SHARE_THROUGHPUT);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getShareThroughput().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addStartAndEndKeys(Map<String, String> headers) {
|
||||
private void addStartAndEndKeys(final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(HttpHeaders.READ_FEED_KEY_TYPE);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
ReadFeedKeyType type = EnumUtils.getEnumIgnoreCase(ReadFeedKeyType.class, value);
|
||||
final ReadFeedKeyType type = EnumUtils.getEnumIgnoreCase(ReadFeedKeyType.class, value);
|
||||
|
||||
if (type == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
HttpHeaders.READ_FEED_KEY_TYPE,
|
||||
value);
|
||||
throw new IllegalStateException(reason);
|
||||
|
@ -1163,7 +1142,7 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
Base64.Decoder decoder = Base64.getDecoder();
|
||||
final Base64.Decoder decoder = Base64.getDecoder();
|
||||
|
||||
value = headers.get(HttpHeaders.START_ID);
|
||||
|
||||
|
@ -1190,27 +1169,27 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
|
||||
private void addSupportSpatialLegacyCoordinates(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.SUPPORT_SPATIAL_LEGACY_COORDINATES);
|
||||
private void addSupportSpatialLegacyCoordinates(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.SUPPORT_SPATIAL_LEGACY_COORDINATES);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getSupportSpatialLegacyCoordinates().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void addUsePolygonsSmallerThanAHemisphere(Map<String, String> headers) {
|
||||
String value = headers.get(HttpHeaders.USE_POLYGONS_SMALLER_THAN_AHEMISPHERE);
|
||||
private void addUsePolygonsSmallerThanAHemisphere(final Map<String, String> headers) {
|
||||
final String value = headers.get(HttpHeaders.USE_POLYGONS_SMALLER_THAN_AHEMISPHERE);
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
this.getUsePolygonsSmallerThanAHemisphere().setValue(Boolean.parseBoolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillTokenFromHeader(Map<String, String> headers, Supplier<RntbdToken> supplier, String name) {
|
||||
private void fillTokenFromHeader(final Map<String, String> headers, final Supplier<RntbdToken> supplier, final String name) {
|
||||
|
||||
String value = headers.get(name);
|
||||
final String value = headers.get(name);
|
||||
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
|
||||
RntbdToken token = supplier.get();
|
||||
final RntbdToken token = supplier.get();
|
||||
|
||||
switch (token.getType()) {
|
||||
|
||||
|
@ -1228,51 +1207,24 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
case Double: {
|
||||
|
||||
Double aDouble = doubleValidator.validate(value, Locale.ROOT);
|
||||
|
||||
if (aDouble == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
name, value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
token.setValue(aDouble);
|
||||
token.setValue(parseDouble(name, value));
|
||||
break;
|
||||
}
|
||||
case Long: {
|
||||
|
||||
Long aLong = longValidator.validate(value, Locale.ROOT);
|
||||
|
||||
if (aLong == null || !longValidator.isInRange(aLong, Integer.MIN_VALUE, Integer.MAX_VALUE)) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue,
|
||||
name, value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
final long aLong = parseLong(name, value, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
token.setValue(aLong);
|
||||
break;
|
||||
}
|
||||
case ULong: {
|
||||
|
||||
Long aLong = longValidator.validate(value, Locale.ROOT);
|
||||
|
||||
if (aLong == null || !longValidator.isInRange(aLong, 0, 0xFFFFFFFFL)) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue, name, value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
final long aLong = parseLong(name, value, 0, 0xFFFFFFFFL);
|
||||
token.setValue(aLong);
|
||||
break;
|
||||
}
|
||||
case LongLong: {
|
||||
|
||||
Long aLong = longValidator.validate(value, Locale.ROOT);
|
||||
|
||||
if (aLong == null) {
|
||||
String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue, name, value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
final long aLong = parseLong(name, value);
|
||||
token.setValue(aLong);
|
||||
break;
|
||||
}
|
||||
|
@ -1284,4 +1236,37 @@ final class RntbdRequestHeaders extends RntbdTokenStream<RntbdRequestHeader> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static double parseDouble(final String name, final String value) {
|
||||
|
||||
final double aDouble;
|
||||
|
||||
try {
|
||||
aDouble = Double.parseDouble(value);
|
||||
} catch (final NumberFormatException error) {
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue, name, value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
return aDouble;
|
||||
}
|
||||
|
||||
private static long parseLong(final String name, final String value) {
|
||||
final long aLong;
|
||||
try {
|
||||
aLong = Long.parseLong(value);
|
||||
} catch (final NumberFormatException error) {
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue, name, value);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
return aLong;
|
||||
}
|
||||
|
||||
private static long parseLong(final String name, final String value, final long min, final long max) {
|
||||
final long aLong = parseLong(name, value);
|
||||
if (!(min <= aLong && aLong <= max)) {
|
||||
final String reason = String.format(Locale.ROOT, RMResources.InvalidRequestHeaderValue, name, aLong);
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
return aLong;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,11 @@
|
|||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.base.Strings;
|
||||
import com.microsoft.azure.cosmosdb.BridgeInternal;
|
||||
import com.microsoft.azure.cosmosdb.DocumentClientException;
|
||||
import com.microsoft.azure.cosmosdb.Error;
|
||||
|
@ -62,116 +60,70 @@ import io.netty.channel.ChannelPipeline;
|
|||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.CoalescingBufferQueue;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.Timeout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.SocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.microsoft.azure.cosmosdb.internal.HttpConstants.StatusCodes;
|
||||
import static com.microsoft.azure.cosmosdb.internal.HttpConstants.SubStatusCodes;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdReporter.reportIssue;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdReporter.reportIssueUnless;
|
||||
|
||||
final public class RntbdRequestManager implements ChannelInboundHandler, ChannelOutboundHandler, ChannelHandler {
|
||||
public final class RntbdRequestManager implements ChannelHandler, ChannelInboundHandler, ChannelOutboundHandler {
|
||||
|
||||
// region Fields
|
||||
|
||||
final private static Logger logger = LoggerFactory.getLogger(RntbdRequestManager.class);
|
||||
final private CompletableFuture<RntbdContext> contextFuture = new CompletableFuture<>();
|
||||
final private CompletableFuture<RntbdContextRequest> contextRequestFuture = new CompletableFuture<>();
|
||||
final private ConcurrentHashMap<UUID, PendingRequest> pendingRequests = new ConcurrentHashMap<>();
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdRequestManager.class);
|
||||
|
||||
private volatile ChannelHandlerContext context;
|
||||
private volatile PendingRequest currentRequest;
|
||||
private volatile CoalescingBufferQueue pendingWrites;
|
||||
private final CompletableFuture<RntbdContext> contextFuture = new CompletableFuture<>();
|
||||
private final CompletableFuture<RntbdContextRequest> contextRequestFuture = new CompletableFuture<>();
|
||||
private final ConcurrentHashMap<UUID, RntbdRequestRecord> pendingRequests = new ConcurrentHashMap<>();
|
||||
|
||||
private boolean closingExceptionally = false;
|
||||
private ChannelHandlerContext context;
|
||||
private RntbdRequestRecord pendingRequest;
|
||||
private CoalescingBufferQueue pendingWrites;
|
||||
|
||||
// endregion
|
||||
|
||||
// region Request management methods
|
||||
// region ChannelHandler methods
|
||||
|
||||
/**
|
||||
* Cancels the {@link CompletableFuture} for the request message identified by @{code activityId}
|
||||
* Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.
|
||||
*
|
||||
* @param activityId identifies an RNTBD request message
|
||||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
public void cancelStoreResponseFuture(UUID activityId) {
|
||||
Objects.requireNonNull(activityId, "activityId");
|
||||
this.removePendingRequest(activityId).getResponseFuture().cancel(true);
|
||||
@Override
|
||||
public void handlerAdded(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(context, "handlerAdded");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails a {@link CompletableFuture} for the request message identified by {@code activityId}
|
||||
* Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events
|
||||
* anymore.
|
||||
*
|
||||
* @param activityId identifies an RNTBD request message
|
||||
* @param cause specifies the cause of the failure
|
||||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
public void completeStoreResponseFutureExceptionally(UUID activityId, Throwable cause) {
|
||||
Objects.requireNonNull(activityId, "activityId");
|
||||
Objects.requireNonNull(cause, "cause");
|
||||
this.removePendingRequest(activityId).getResponseFuture().completeExceptionally(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CompletableFuture} of a {@link StoreResponse} for the message identified by {@code activityId}
|
||||
*
|
||||
* @param requestArgs identifies a request message
|
||||
* @return a {@link CompletableFuture} of a {@link StoreResponse}
|
||||
*/
|
||||
public CompletableFuture<StoreResponse> createStoreResponseFuture(RntbdRequestArgs requestArgs) {
|
||||
|
||||
Objects.requireNonNull(requestArgs, "requestArgs");
|
||||
|
||||
this.currentRequest = this.pendingRequests.compute(requestArgs.getActivityId(), (activityId, pendingRequest) -> {
|
||||
|
||||
if (pendingRequest == null) {
|
||||
pendingRequest = new PendingRequest(requestArgs);
|
||||
logger.trace("{} created new pending request", pendingRequest);
|
||||
} else {
|
||||
logger.trace("{} renewed existing pending request", pendingRequest);
|
||||
}
|
||||
|
||||
return pendingRequest;
|
||||
|
||||
});
|
||||
|
||||
this.traceOperation(logger, this.context, "createStoreResponseFuture");
|
||||
return this.currentRequest.getResponseFuture();
|
||||
}
|
||||
|
||||
void traceOperation(Logger logger, ChannelHandlerContext context, String operationName, Object... args) {
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
||||
final long birthTime;
|
||||
final BigDecimal lifetime;
|
||||
|
||||
if (this.currentRequest == null) {
|
||||
birthTime = System.nanoTime();
|
||||
lifetime = BigDecimal.ZERO;
|
||||
} else {
|
||||
birthTime = this.currentRequest.getBirthTime();
|
||||
lifetime = BigDecimal.valueOf(this.currentRequest.getLifetime().toNanos(), 6);
|
||||
}
|
||||
|
||||
logger.info("{},{},\"{}({})\",\"{}\",\"{}\"", birthTime, lifetime, operationName, Stream.of(args).map(arg ->
|
||||
arg == null ? "null" : arg.toString()).collect(Collectors.joining(",")
|
||||
), this.currentRequest, context
|
||||
);
|
||||
}
|
||||
@Override
|
||||
public void handlerRemoved(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(context, "handlerRemoved");
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
@ -184,61 +136,29 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(logger, this.context, "channelActive");
|
||||
public void channelActive(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(this.context, "channelActive");
|
||||
context.fireChannelActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes all pending requests exceptionally when a channel reaches the end of its lifetime
|
||||
* <p>
|
||||
* This method will only be called after the channel is closed.
|
||||
*
|
||||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext context) throws Exception {
|
||||
|
||||
this.traceOperation(logger, this.context, "channelInactive");
|
||||
Channel channel = context.channel();
|
||||
|
||||
try {
|
||||
|
||||
this.contextRequestFuture.getNow(null);
|
||||
this.contextFuture.getNow(null);
|
||||
|
||||
logger.debug("{} INACTIVE: RNTBD negotiation request status:\nrequest: {}\nresponse: {}",
|
||||
channel, this.contextRequestFuture, this.contextFuture
|
||||
);
|
||||
|
||||
} catch (CancellationException error) {
|
||||
logger.debug("{} INACTIVE: RNTBD negotiation request cancelled:", channel, error);
|
||||
|
||||
} catch (Exception error) {
|
||||
logger.error("{} INACTIVE: RNTBD negotiation request failed:", channel, error);
|
||||
}
|
||||
|
||||
if (!this.pendingWrites.isEmpty()) {
|
||||
this.pendingWrites.releaseAndFailAll(context, new ChannelException("Closed with pending writes"));
|
||||
}
|
||||
|
||||
if (!this.pendingRequests.isEmpty()) {
|
||||
|
||||
String reason = String.format("%s Closed with pending requests", channel);
|
||||
ChannelException cause = new ChannelException(reason);
|
||||
|
||||
for (PendingRequest pendingRequest : this.pendingRequests.values()) {
|
||||
pendingRequest.getResponseFuture().completeExceptionally(cause);
|
||||
}
|
||||
|
||||
this.pendingRequests.clear();
|
||||
}
|
||||
|
||||
public void channelInactive(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(this.context, "channelInactive");
|
||||
this.completeAllPendingRequestsExceptionally(context, ClosedWithPendingRequestsException.INSTANCE);
|
||||
context.fireChannelInactive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext context, Object message) throws Exception {
|
||||
public void channelRead(final ChannelHandlerContext context, final Object message) throws Exception {
|
||||
|
||||
this.traceOperation(logger, context, "channelRead");
|
||||
this.traceOperation(context, "channelRead");
|
||||
|
||||
if (message instanceof RntbdResponse) {
|
||||
try {
|
||||
|
@ -246,11 +166,11 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
} finally {
|
||||
ReferenceCountUtil.release(message);
|
||||
}
|
||||
this.traceOperation(logger, context, "messageReceived");
|
||||
this.traceOperation(context, "channelReadComplete");
|
||||
return;
|
||||
}
|
||||
|
||||
String reason = String.format("Expected message of type %s, not %s", RntbdResponse.class, message.getClass());
|
||||
final String reason = Strings.lenientFormat("expected message of type %s, not %s", RntbdResponse.class, message.getClass());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
|
@ -264,14 +184,14 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(logger, context, "channelReadComplete");
|
||||
public void channelReadComplete(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(context, "channelReadComplete");
|
||||
context.fireChannelReadComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link CoalescingBufferQueue} for buffering encoded requests until we have an {@link RntbdRequest}
|
||||
*
|
||||
* <p>
|
||||
* This method then calls {@link ChannelHandlerContext#fireChannelRegistered()} to forward to the next
|
||||
* {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||
* <p>
|
||||
|
@ -280,13 +200,13 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @param context the {@link ChannelHandlerContext} for which the bind operation is made
|
||||
*/
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext context) throws Exception {
|
||||
public void channelRegistered(final ChannelHandlerContext context) throws Exception {
|
||||
|
||||
this.traceOperation(logger, context, "channelRegistered");
|
||||
this.traceOperation(context, "channelRegistered");
|
||||
|
||||
if (!(this.context == null && this.pendingWrites == null)) {
|
||||
throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
|
||||
this.pendingWrites = new CoalescingBufferQueue(context.channel());
|
||||
this.context = context;
|
||||
|
@ -299,13 +219,13 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
@Override
|
||||
public void channelUnregistered(ChannelHandlerContext context) throws Exception {
|
||||
public void channelUnregistered(final ChannelHandlerContext context) throws Exception {
|
||||
|
||||
this.traceOperation(logger, context, "channelUnregistered");
|
||||
this.traceOperation(context, "channelUnregistered");
|
||||
|
||||
if (this.context == null || this.pendingWrites == null || !this.pendingWrites.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
|
||||
this.pendingWrites = null;
|
||||
this.context = null;
|
||||
|
@ -319,8 +239,8 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(logger, context, "channelWritabilityChanged");
|
||||
public void channelWritabilityChanged(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(context, "channelWritabilityChanged");
|
||||
context.fireChannelWritabilityChanged();
|
||||
}
|
||||
|
||||
|
@ -332,46 +252,30 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
*/
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(final ChannelHandlerContext context, final Throwable cause) throws Exception {
|
||||
|
||||
// TODO: DANOBLE: replace RntbdRequestManager.exceptionCaught with read/write listeners
|
||||
// Notes:
|
||||
// ChannelInboundHandler.exceptionCaught is deprecated and--today, prior to deprecation--only catches read--
|
||||
// i.e., inbound--exceptions.
|
||||
// Replacements:
|
||||
// * read listener: unclear as there is no obvious replacement
|
||||
// * write listener: implemented by RntbdTransportClient.DefaultEndpoint.doWrite
|
||||
// ChannelInboundHandler.exceptionCaught is deprecated and--today, prior to deprecation--only catches read--
|
||||
// i.e., inbound--exceptions.
|
||||
// Replacements:
|
||||
// * read listener: unclear as there is no obvious replacement
|
||||
// * write listener: implemented by RntbdTransportClient.DefaultEndpoint.doWrite
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/373213
|
||||
|
||||
logger.error("{} closing exceptionally: {}", context.channel(), cause.getMessage());
|
||||
traceOperation(logger, context, "exceptionCaught", cause);
|
||||
context.close();
|
||||
this.traceOperation(context, "exceptionCaught", cause);
|
||||
|
||||
if (!this.closingExceptionally) {
|
||||
reportIssueUnless(cause != ClosedWithPendingRequestsException.INSTANCE, logger, context,
|
||||
"expected an exception other than ", ClosedWithPendingRequestsException.INSTANCE);
|
||||
this.completeAllPendingRequestsExceptionally(context, cause);
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.
|
||||
*
|
||||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(logger, context, "handlerAdded");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events
|
||||
* anymore.
|
||||
*
|
||||
* @param context {@link ChannelHandlerContext} to which this {@link RntbdRequestManager} belongs
|
||||
*/
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(logger, context, "handlerRemoved");
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes inbound events triggered by channel handlers in the {@link RntbdClientChannelInitializer} pipeline
|
||||
* Processes inbound events triggered by channel handlers in the {@link RntbdClientChannelHandler} pipeline
|
||||
* <p>
|
||||
* All but inbound request management events are ignored.
|
||||
*
|
||||
|
@ -379,12 +283,15 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @param event An object representing a user event
|
||||
*/
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext context, Object event) throws Exception {
|
||||
this.traceOperation(logger, context, "userEventTriggered", event);
|
||||
public void userEventTriggered(final ChannelHandlerContext context, final Object event) {
|
||||
|
||||
this.traceOperation(context, "userEventTriggered", event);
|
||||
|
||||
if (event instanceof RntbdContext) {
|
||||
this.completeRntbdContextFuture(context, (RntbdContext)event);
|
||||
return;
|
||||
}
|
||||
|
||||
context.fireUserEventTriggered(event);
|
||||
}
|
||||
|
||||
|
@ -401,11 +308,24 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void bind(ChannelHandlerContext context, SocketAddress localAddress, ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(logger, context, "bind");
|
||||
public void bind(final ChannelHandlerContext context, final SocketAddress localAddress, final ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(context, "bind");
|
||||
context.bind(localAddress, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a close operation is made.
|
||||
*
|
||||
* @param context the {@link ChannelHandlerContext} for which the close operation is made
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void close(final ChannelHandlerContext context, final ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(context, "close");
|
||||
context.close(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a connect operation is made.
|
||||
*
|
||||
|
@ -416,37 +336,11 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void connect(ChannelHandlerContext context, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(logger, context, "connect");
|
||||
public void connect(final ChannelHandlerContext context, final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(context, "connect");
|
||||
context.connect(remoteAddress, localAddress, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a disconnect operation is made.
|
||||
*
|
||||
* @param context the {@link ChannelHandlerContext} for which the disconnect operation is made
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void disconnect(ChannelHandlerContext context, ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(logger, context, "disconnect");
|
||||
context.disconnect(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a close operation is made.
|
||||
*
|
||||
* @param context the {@link ChannelHandlerContext} for which the close operation is made
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void close(ChannelHandlerContext context, ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(logger, context, "close");
|
||||
context.close(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a deregister operation is made from the current registered {@link EventLoop}.
|
||||
*
|
||||
|
@ -455,11 +349,24 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void deregister(ChannelHandlerContext context, ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(logger, context, "deregister");
|
||||
public void deregister(final ChannelHandlerContext context, final ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(context, "deregister");
|
||||
context.deregister(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a disconnect operation is made.
|
||||
*
|
||||
* @param context the {@link ChannelHandlerContext} for which the disconnect operation is made
|
||||
* @param promise the {@link ChannelPromise} to notify once the operation completes
|
||||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void disconnect(final ChannelHandlerContext context, final ChannelPromise promise) throws Exception {
|
||||
this.traceOperation(context, "disconnect");
|
||||
context.disconnect(promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once a flush operation is made
|
||||
* <p>
|
||||
|
@ -469,8 +376,8 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void flush(ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(logger, context, "flush");
|
||||
public void flush(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(context, "flush");
|
||||
context.flush();
|
||||
}
|
||||
|
||||
|
@ -480,8 +387,8 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @param context the {@link ChannelHandlerContext} for which the read operation is made
|
||||
*/
|
||||
@Override
|
||||
public void read(ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(logger, context, "read");
|
||||
public void read(final ChannelHandlerContext context) throws Exception {
|
||||
this.traceOperation(context, "read");
|
||||
context.read();
|
||||
}
|
||||
|
||||
|
@ -497,24 +404,28 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
* @throws Exception thrown if an error occurs
|
||||
*/
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object message, ChannelPromise promise) throws Exception {
|
||||
public void write(final ChannelHandlerContext context, final Object message, final ChannelPromise promise) throws Exception {
|
||||
|
||||
this.traceOperation(logger, context, "write", message);
|
||||
// TODO: DANOBLE: Ensure that all write errors are reported with a root cause of type EncoderException
|
||||
|
||||
if (message instanceof RntbdRequestArgs) {
|
||||
this.currentRequest = this.getPendingRequest((RntbdRequestArgs)message);
|
||||
context.write(message, promise);
|
||||
return;
|
||||
this.traceOperation(context, "write", message);
|
||||
|
||||
if (message instanceof RntbdRequestRecord) {
|
||||
context.write(this.addPendingRequestRecord((RntbdRequestRecord)message), promise);
|
||||
} else {
|
||||
final String reason = Strings.lenientFormat("Expected message of type %s, not %s", RntbdRequestArgs.class, message.getClass());
|
||||
this.exceptionCaught(context, new IllegalStateException(reason));
|
||||
}
|
||||
|
||||
String reason = String.format("Expected message of type %s, not %s", RntbdRequestArgs.class, message.getClass());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Private and package private methods
|
||||
|
||||
int getPendingRequestCount() {
|
||||
return this.pendingRequests.size();
|
||||
}
|
||||
|
||||
CompletableFuture<RntbdContextRequest> getRntbdContextRequestFuture() {
|
||||
return this.contextRequestFuture;
|
||||
}
|
||||
|
@ -523,82 +434,175 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
return this.contextFuture.getNow(null) != null;
|
||||
}
|
||||
|
||||
void pendWrite(ByteBuf out, ChannelPromise promise) {
|
||||
void pendWrite(final ByteBuf out, final ChannelPromise promise) {
|
||||
|
||||
Objects.requireNonNull(out, "out");
|
||||
|
||||
if (this.pendingWrites == null) {
|
||||
throw new IllegalStateException("pendingWrites: null");
|
||||
}
|
||||
checkNotNull(out, "out");
|
||||
checkNotNull(promise, "promise");
|
||||
checkState(this.pendingWrites != null, "pendingWrite: null");
|
||||
|
||||
this.pendingWrites.add(out, promise);
|
||||
}
|
||||
|
||||
private PendingRequest checkPendingRequest(UUID activityId, PendingRequest pendingRequest) {
|
||||
private RntbdRequestArgs addPendingRequestRecord(final RntbdRequestRecord requestRecord) {
|
||||
|
||||
if (pendingRequest == null) {
|
||||
throw new IllegalStateException(String.format("Pending request not found: %s", activityId));
|
||||
}
|
||||
// TODO: DANOBLE: Consider revising the implementation of RntbdRequestManager.addPendingRequestRecord
|
||||
// At a minimum consider these issues:
|
||||
// * Do we have a requirement to support multiple concurrent operations for a single activityId?
|
||||
// * Should we replace, renew, or maintain a list of pending requests for each activityId?
|
||||
// We currently fail when we find an existing request record.
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/378801
|
||||
|
||||
if (pendingRequest.getResponseFuture().isDone()) {
|
||||
throw new IllegalStateException(String.format("Request is not pending: %s", activityId));
|
||||
}
|
||||
this.pendingRequest = this.pendingRequests.compute(requestRecord.getActivityId(), (activityId, current) -> {
|
||||
|
||||
return pendingRequest;
|
||||
}
|
||||
checkArgument(current == null, "current: expected no request record, not %s", current);
|
||||
|
||||
private void completeRntbdContextFuture(ChannelHandlerContext context, RntbdContext value) {
|
||||
final Timeout pendingRequestTimeout = requestRecord.newTimeout(timeout -> {
|
||||
this.pendingRequests.remove(activityId);
|
||||
requestRecord.expire();
|
||||
});
|
||||
|
||||
Objects.requireNonNull(context, "context");
|
||||
Objects.requireNonNull(value, "value");
|
||||
requestRecord.whenComplete((response, error) -> {
|
||||
this.pendingRequests.remove(activityId);
|
||||
pendingRequestTimeout.cancel();
|
||||
});
|
||||
|
||||
if (this.contextFuture.isDone()) {
|
||||
throw new IllegalStateException(String.format("rntbdContextFuture: %s", this.contextFuture));
|
||||
}
|
||||
return requestRecord;
|
||||
});
|
||||
|
||||
contextFuture.complete(value);
|
||||
|
||||
RntbdContextNegotiator negotiator = context.channel().pipeline().get(RntbdContextNegotiator.class);
|
||||
negotiator.removeInboundHandler();
|
||||
negotiator.removeOutboundHandler();
|
||||
|
||||
if (!pendingWrites.isEmpty()) {
|
||||
this.pendingWrites.writeAndRemoveAll(context);
|
||||
}
|
||||
}
|
||||
|
||||
private PendingRequest getPendingRequest(RntbdRequestArgs args) {
|
||||
UUID activityId = args.getActivityId();
|
||||
return checkPendingRequest(activityId, this.pendingRequests.get(activityId));
|
||||
return this.pendingRequest.getArgs();
|
||||
}
|
||||
|
||||
private Optional<RntbdContext> getRntbdContext() {
|
||||
return Optional.of(this.contextFuture.getNow(null));
|
||||
}
|
||||
|
||||
private void completeAllPendingRequestsExceptionally(final ChannelHandlerContext context, final Throwable throwable) {
|
||||
|
||||
checkNotNull(throwable, "throwable: null");
|
||||
|
||||
if (this.closingExceptionally) {
|
||||
checkArgument(throwable == ClosedWithPendingRequestsException.INSTANCE, "throwable: %s", throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
this.closingExceptionally = true;
|
||||
|
||||
if (!this.pendingWrites.isEmpty()) {
|
||||
this.pendingWrites.releaseAndFailAll(context, ClosedWithPendingRequestsException.INSTANCE);
|
||||
}
|
||||
|
||||
if (!this.pendingRequests.isEmpty()) {
|
||||
|
||||
final Channel channel = context.channel();
|
||||
|
||||
if (!this.contextRequestFuture.isDone()) {
|
||||
this.contextRequestFuture.completeExceptionally(throwable);
|
||||
}
|
||||
|
||||
if (!this.contextFuture.isDone()) {
|
||||
this.contextFuture.completeExceptionally(throwable);
|
||||
}
|
||||
|
||||
Exception contextRequestException = null;
|
||||
|
||||
if (this.contextRequestFuture.isCompletedExceptionally()) {
|
||||
try {
|
||||
this.contextRequestFuture.get();
|
||||
} catch (final CancellationException error) {
|
||||
logger.debug("\n {} closed: context send cancelled", channel);
|
||||
contextRequestException = error;
|
||||
} catch (final Throwable error) {
|
||||
final String message = Strings.lenientFormat("context send failed due to %s", error);
|
||||
logger.debug("\n {} closed: {}", channel, message);
|
||||
contextRequestException = new ChannelException(message, error);
|
||||
}
|
||||
} else if (this.contextFuture.isCompletedExceptionally()) {
|
||||
try {
|
||||
this.contextFuture.get();
|
||||
} catch (final CancellationException error) {
|
||||
logger.debug("\n {} closed: context receive cancelled", channel);
|
||||
contextRequestException = error;
|
||||
} catch (final Throwable error) {
|
||||
final String message = Strings.lenientFormat("context receive failed due to %s", error);
|
||||
logger.debug("\n {} closed: {}", channel, message);
|
||||
contextRequestException = new ChannelException(message, error);
|
||||
}
|
||||
}
|
||||
|
||||
final String origin = "rntbd:/" + channel.remoteAddress();
|
||||
final int count = this.pendingRequests.size();
|
||||
final String message;
|
||||
|
||||
if (contextRequestException == null) {
|
||||
message = Strings.lenientFormat("%s channel closed with %s pending requests", channel, count);
|
||||
} else {
|
||||
message = Strings.lenientFormat("%s context request failed with %s pending requests", channel, count);
|
||||
}
|
||||
|
||||
final Exception reason;
|
||||
|
||||
if (throwable == ClosedWithPendingRequestsException.INSTANCE && contextRequestException != null) {
|
||||
reason = contextRequestException;
|
||||
} else {
|
||||
reason = throwable instanceof Exception ? (Exception)throwable : new ChannelException(throwable);
|
||||
}
|
||||
|
||||
for (final RntbdRequestRecord requestRecord : this.pendingRequests.values()) {
|
||||
|
||||
final RntbdRequestArgs args = requestRecord.getArgs();
|
||||
final String requestUri = origin + args.getReplicaPath();
|
||||
final Map<String, String> headers = args.getServiceRequest().getHeaders();
|
||||
|
||||
final GoneException cause = new GoneException(message, reason, headers, requestUri);
|
||||
BridgeInternal.setRequestHeaders(cause, headers);
|
||||
requestRecord.completeExceptionally(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void completeRntbdContextFuture(final ChannelHandlerContext context, final RntbdContext value) {
|
||||
|
||||
checkNotNull(context, "context");
|
||||
checkNotNull(value, "value");
|
||||
|
||||
if (this.contextFuture.isDone()) {
|
||||
throw new IllegalStateException(Strings.lenientFormat("rntbdContextFuture: %s", this.contextFuture));
|
||||
}
|
||||
|
||||
this.contextFuture.complete(value);
|
||||
|
||||
final RntbdContextNegotiator negotiator = context.channel().pipeline().get(RntbdContextNegotiator.class);
|
||||
negotiator.removeInboundHandler();
|
||||
negotiator.removeOutboundHandler();
|
||||
|
||||
if (!this.pendingWrites.isEmpty()) {
|
||||
this.pendingWrites.writeAndRemoveAll(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called for each incoming message of type {@link StoreResponse} to complete a request
|
||||
*
|
||||
* @param context {@link ChannelHandlerContext} encode to which this {@link RntbdRequestManager} belongs
|
||||
* @param response the message encode handle
|
||||
*/
|
||||
private void messageReceived(ChannelHandlerContext context, RntbdResponse response) {
|
||||
private void messageReceived(final ChannelHandlerContext context, final RntbdResponse response) {
|
||||
|
||||
final UUID activityId = response.getActivityId();
|
||||
final PendingRequest pendingRequest = this.pendingRequests.remove(activityId);
|
||||
final RntbdRequestRecord pendingRequest = this.pendingRequests.get(activityId);
|
||||
|
||||
if (pendingRequest == null) {
|
||||
logger.warn("[activityId: {}] no request pending", activityId);
|
||||
return;
|
||||
}
|
||||
|
||||
final CompletableFuture<StoreResponse> future = pendingRequest.getResponseFuture();
|
||||
final HttpResponseStatus status = response.getStatus();
|
||||
|
||||
if (HttpResponseStatus.OK.code() <= status.code() && status.code() < HttpResponseStatus.MULTIPLE_CHOICES.code()) {
|
||||
|
||||
final StoreResponse storeResponse = response.toStoreResponse(this.contextFuture.getNow(null));
|
||||
future.complete(storeResponse);
|
||||
pendingRequest.complete(storeResponse);
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -613,25 +617,9 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
|
||||
// ..Create Error instance
|
||||
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
final Error error;
|
||||
|
||||
if (response.hasPayload()) {
|
||||
|
||||
try (Reader reader = response.getResponseStreamReader()) {
|
||||
|
||||
error = BridgeInternal.createError((ObjectNode)mapper.readTree(reader));
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
String message = String.format("%s: %s", e.getClass(), e.getMessage());
|
||||
logger.error("{} %s", context.channel(), message);
|
||||
throw new CorruptedFrameException(message);
|
||||
}
|
||||
|
||||
} else {
|
||||
error = new Error(Integer.toString(status.code()), status.reasonPhrase(), status.codeClass().name());
|
||||
}
|
||||
final Error error = response.hasPayload() ?
|
||||
BridgeInternal.createError(RntbdObjectMapper.readTree(response)) :
|
||||
new Error(Integer.toString(status.code()), status.reasonPhrase(), status.codeClass().name());
|
||||
|
||||
// ..Map RNTBD response headers to HTTP response headers
|
||||
|
||||
|
@ -657,7 +645,7 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
|
||||
case StatusCodes.GONE:
|
||||
|
||||
int subStatusCode = Math.toIntExact(response.getHeader(RntbdResponseHeader.SubStatus));
|
||||
final int subStatusCode = Math.toIntExact(response.getHeader(RntbdResponseHeader.SubStatus));
|
||||
|
||||
switch (subStatusCode) {
|
||||
case SubStatusCodes.COMPLETING_SPLIT:
|
||||
|
@ -727,52 +715,58 @@ final public class RntbdRequestManager implements ChannelInboundHandler, Channel
|
|||
break;
|
||||
}
|
||||
|
||||
logger.trace("{}[activityId: {}, statusCode: {}, subStatusCode: {}] {}",
|
||||
context.channel(), cause.getActivityId(), cause.getStatusCode(), cause.getSubStatusCode(),
|
||||
cause.getMessage()
|
||||
logger.trace("{}[activityId: {}, statusCode: {}, subStatusCode: {}] ",
|
||||
context.channel(), cause.getActivityId(), cause.getStatusCode(), cause.getSubStatusCode(), cause
|
||||
);
|
||||
|
||||
future.completeExceptionally(cause);
|
||||
pendingRequest.completeExceptionally(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private PendingRequest removePendingRequest(UUID activityId) {
|
||||
PendingRequest pendingRequest = this.pendingRequests.remove(activityId);
|
||||
return checkPendingRequest(activityId, pendingRequest);
|
||||
private void traceOperation(final ChannelHandlerContext context, final String operationName, final Object... args) {
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
||||
final long birthTime;
|
||||
final BigDecimal lifetime;
|
||||
|
||||
if (this.pendingRequest == null) {
|
||||
birthTime = System.nanoTime();
|
||||
lifetime = BigDecimal.ZERO;
|
||||
} else {
|
||||
birthTime = this.pendingRequest.getBirthTime();
|
||||
lifetime = BigDecimal.valueOf(this.pendingRequest.getLifetime().toNanos(), 6);
|
||||
}
|
||||
|
||||
logger.trace("{},{},\"{}({})\",\"{}\",\"{}\"", birthTime, lifetime, operationName, Stream.of(args).map(arg ->
|
||||
arg == null ? "null" : arg.toString()).collect(Collectors.joining(",")
|
||||
), this.pendingRequest, context
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Types
|
||||
|
||||
private static class PendingRequest {
|
||||
private static class ClosedWithPendingRequestsException extends RuntimeException {
|
||||
|
||||
private final RntbdRequestArgs args;
|
||||
private final CompletableFuture<StoreResponse> responseFuture = new CompletableFuture<>();
|
||||
static ClosedWithPendingRequestsException INSTANCE = new ClosedWithPendingRequestsException();
|
||||
|
||||
PendingRequest(RntbdRequestArgs args) {
|
||||
this.args = args;
|
||||
}
|
||||
// TODO: DANOBLE: Consider revising strategy for closing an RntbdTransportClient with pending requests
|
||||
// One possibility:
|
||||
// A channel associated with an RntbdTransportClient will not be closed immediately, if there are any pending
|
||||
// requests on it. Instead it will be scheduled to close after the request timeout interval (default: 60s) has
|
||||
// elapsed.
|
||||
// Algorithm:
|
||||
// When the RntbdTransportClient is closed, it closes each of its RntbdServiceEndpoint instances. In turn each
|
||||
// RntbdServiceEndpoint closes its RntbdClientChannelPool. The RntbdClientChannelPool.close method should
|
||||
// schedule closure of any channel with pending requests for later; when the request timeout interval has
|
||||
// elapsed or--ideally--when all pending requests have completed.
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/388987
|
||||
|
||||
RntbdRequestArgs getArgs() {
|
||||
return this.args;
|
||||
}
|
||||
|
||||
long getBirthTime() {
|
||||
return this.args.getBirthTime();
|
||||
}
|
||||
|
||||
Duration getLifetime() {
|
||||
return this.args.getLifetime();
|
||||
}
|
||||
|
||||
CompletableFuture<StoreResponse> getResponseFuture() {
|
||||
return this.responseFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.args.toString();
|
||||
private ClosedWithPendingRequestsException() {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.microsoft.azure.cosmosdb.BridgeInternal;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.RequestTimeoutException;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.StoreResponse;
|
||||
import io.netty.util.Timeout;
|
||||
import io.netty.util.TimerTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public final class RntbdRequestRecord extends CompletableFuture<StoreResponse> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdRequestRecord.class);
|
||||
|
||||
private final RntbdRequestArgs args;
|
||||
private final RntbdRequestTimer timer;
|
||||
|
||||
public RntbdRequestRecord(final RntbdRequestArgs args, final RntbdRequestTimer timer) {
|
||||
|
||||
checkNotNull(args, "args");
|
||||
checkNotNull(timer, "timer");
|
||||
|
||||
this.args = args;
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
public UUID getActivityId() {
|
||||
return this.args.getActivityId();
|
||||
}
|
||||
|
||||
public RntbdRequestArgs getArgs() {
|
||||
return this.args;
|
||||
}
|
||||
|
||||
public long getBirthTime() {
|
||||
return this.args.getBirthTime();
|
||||
}
|
||||
|
||||
public Duration getLifetime() {
|
||||
return this.args.getLifetime();
|
||||
}
|
||||
|
||||
public void expire() {
|
||||
final RequestTimeoutException error = new RequestTimeoutException(
|
||||
String.format("Request timeout interval (%,d ms) elapsed",
|
||||
this.timer.getRequestTimeout(TimeUnit.MILLISECONDS)),
|
||||
this.args.getPhysicalAddress());
|
||||
BridgeInternal.setRequestHeaders(error, this.args.getServiceRequest().getHeaders());
|
||||
this.completeExceptionally(error);
|
||||
}
|
||||
|
||||
public Timeout newTimeout(final TimerTask task) {
|
||||
return this.timer.newTimeout(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.args.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import io.netty.util.HashedWheelTimer;
|
||||
import io.netty.util.Timeout;
|
||||
import io.netty.util.Timer;
|
||||
import io.netty.util.TimerTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class RntbdRequestTimer implements AutoCloseable {
|
||||
|
||||
private static final long FIVE_MILLISECONDS = 5000000L;
|
||||
private final long requestTimeout;
|
||||
private final Timer timer;
|
||||
|
||||
public RntbdRequestTimer(final long requestTimeout) {
|
||||
|
||||
// Inspection of the HashWheelTimer code indicates that our choice of a 5 millisecond timer resolution ensures
|
||||
// a request will timeout within 10 milliseconds of the specified requestTimeout interval. This is because
|
||||
// cancellation of a timeout takes two timer resolution units to complete.
|
||||
|
||||
this.timer = new HashedWheelTimer(FIVE_MILLISECONDS, TimeUnit.NANOSECONDS);
|
||||
this.requestTimeout = requestTimeout;
|
||||
}
|
||||
|
||||
public long getRequestTimeout(TimeUnit unit) {
|
||||
return unit.convert(requestTimeout, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws RuntimeException {
|
||||
this.timer.stop();
|
||||
}
|
||||
|
||||
public Timeout newTimeout(final TimerTask task) {
|
||||
return this.timer.newTimeout(task, this.requestTimeout, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
}
|
|
@ -35,7 +35,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
|||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.StoreResponse;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.EmptyByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
@ -44,48 +43,46 @@ import io.netty.util.ReferenceCounted;
|
|||
import io.netty.util.ResourceLeakDetector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.RntbdResponseHeader;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@JsonPropertyOrder({ "frame", "headers", "content" })
|
||||
final public class RntbdResponse implements ReferenceCounted {
|
||||
public final class RntbdResponse implements ReferenceCounted {
|
||||
|
||||
// region Fields
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = PayloadSerializer.class)
|
||||
@JsonProperty
|
||||
final private ByteBuf content;
|
||||
private final ByteBuf content;
|
||||
|
||||
@JsonProperty
|
||||
final private RntbdResponseStatus frame;
|
||||
private final RntbdResponseStatus frame;
|
||||
|
||||
@JsonProperty
|
||||
final private RntbdResponseHeaders headers;
|
||||
private final RntbdResponseHeaders headers;
|
||||
|
||||
private AtomicInteger referenceCount = new AtomicInteger();
|
||||
|
||||
// endregion
|
||||
|
||||
public RntbdResponse(UUID activityId, int statusCode, Map<String, String> map, ByteBuf content) {
|
||||
public RntbdResponse(final UUID activityId, final int statusCode, final Map<String, String> map, final ByteBuf content) {
|
||||
|
||||
this.headers = RntbdResponseHeaders.fromMap(map, content.readableBytes() > 0);
|
||||
this.content = content.retain();
|
||||
|
||||
HttpResponseStatus status = HttpResponseStatus.valueOf(statusCode);
|
||||
int length = RntbdResponseStatus.LENGTH + headers.computeLength();
|
||||
final HttpResponseStatus status = HttpResponseStatus.valueOf(statusCode);
|
||||
final int length = RntbdResponseStatus.LENGTH + this.headers.computeLength();
|
||||
|
||||
this.frame = new RntbdResponseStatus(length, status, activityId);
|
||||
}
|
||||
|
||||
private RntbdResponse(RntbdResponseStatus frame, RntbdResponseHeaders headers, ByteBuf content) {
|
||||
private RntbdResponse(final RntbdResponseStatus frame, final RntbdResponseHeaders headers, final ByteBuf content) {
|
||||
|
||||
this.frame = frame;
|
||||
this.headers = headers;
|
||||
|
@ -106,17 +103,12 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
return this.headers;
|
||||
}
|
||||
|
||||
public InputStreamReader getResponseStreamReader() {
|
||||
InputStream istream = new ByteBufInputStream(this.content.retain(), true);
|
||||
return new InputStreamReader(istream);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public HttpResponseStatus getStatus() {
|
||||
return this.frame.getStatus();
|
||||
}
|
||||
|
||||
static RntbdResponse decode(ByteBuf in) {
|
||||
static RntbdResponse decode(final ByteBuf in) {
|
||||
|
||||
in.markReaderIndex();
|
||||
|
||||
|
@ -124,7 +116,7 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
final RntbdResponseHeaders headers = RntbdResponseHeaders.decode(in.readSlice(frame.getHeadersLength()));
|
||||
|
||||
final boolean hasPayload = headers.isPayloadPresent();
|
||||
ByteBuf content;
|
||||
final ByteBuf content;
|
||||
|
||||
if (hasPayload) {
|
||||
|
||||
|
@ -143,14 +135,14 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
return new RntbdResponse(frame, headers, content);
|
||||
}
|
||||
|
||||
public void encode(ByteBuf out) {
|
||||
public void encode(final ByteBuf out) {
|
||||
|
||||
int start = out.writerIndex();
|
||||
final int start = out.writerIndex();
|
||||
|
||||
this.frame.encode(out);
|
||||
this.headers.encode(out);
|
||||
|
||||
int length = out.writerIndex() - start;
|
||||
final int length = out.writerIndex() - start;
|
||||
|
||||
if (length != this.frame.getLength()) {
|
||||
throw new IllegalStateException();
|
||||
|
@ -165,8 +157,8 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
}
|
||||
|
||||
@JsonIgnore
|
||||
public <T> T getHeader(RntbdResponseHeader header) {
|
||||
T value = (T)this.headers.get(header).getValue();
|
||||
public <T> T getHeader(final RntbdResponseHeader header) {
|
||||
final T value = (T)this.headers.get(header).getValue();
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -199,7 +191,7 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
* @return {@code true} if and only if the reference count became {@code 0} and this object has been de-allocated
|
||||
*/
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
public boolean release(final int decrement) {
|
||||
|
||||
return this.referenceCount.getAndAccumulate(decrement, (value, n) -> {
|
||||
value = value - min(value, n);
|
||||
|
@ -227,15 +219,15 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
* @param increment amount of the increase
|
||||
*/
|
||||
@Override
|
||||
public ReferenceCounted retain(int increment) {
|
||||
public ReferenceCounted retain(final int increment) {
|
||||
this.referenceCount.addAndGet(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
StoreResponse toStoreResponse(RntbdContext context) {
|
||||
StoreResponse toStoreResponse(final RntbdContext context) {
|
||||
|
||||
Objects.requireNonNull(context);
|
||||
int length = this.content.readableBytes();
|
||||
checkNotNull(context, "context");
|
||||
final int length = this.content.readableBytes();
|
||||
|
||||
return new StoreResponse(
|
||||
this.getStatus().code(),
|
||||
|
@ -246,10 +238,10 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
final ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
try {
|
||||
return writer.writeValueAsString(this);
|
||||
} catch (JsonProcessingException error) {
|
||||
} catch (final JsonProcessingException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
}
|
||||
|
@ -274,7 +266,7 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
* @param hint information useful for debugging (unused)
|
||||
*/
|
||||
@Override
|
||||
public ReferenceCounted touch(Object hint) {
|
||||
public ReferenceCounted touch(final Object hint) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -285,9 +277,9 @@ final public class RntbdResponse implements ReferenceCounted {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void serialize(ByteBuf value, JsonGenerator generator, SerializerProvider provider) throws IOException {
|
||||
public void serialize(final ByteBuf value, final JsonGenerator generator, final SerializerProvider provider) throws IOException {
|
||||
|
||||
int length = value.readableBytes();
|
||||
final int length = value.readableBytes();
|
||||
|
||||
generator.writeStartObject();
|
||||
generator.writeObjectField("length", length);
|
||||
|
|
|
@ -32,9 +32,9 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
final public class RntbdResponseDecoder extends ByteToMessageDecoder {
|
||||
public final class RntbdResponseDecoder extends ByteToMessageDecoder {
|
||||
|
||||
final private static Logger Logger = LoggerFactory.getLogger(RntbdResponseDecoder.class);
|
||||
private static final Logger Logger = LoggerFactory.getLogger(RntbdResponseDecoder.class);
|
||||
|
||||
/**
|
||||
* Deserialize from an input {@link ByteBuf} to an {@link RntbdResponse} instance
|
||||
|
@ -46,11 +46,11 @@ final public class RntbdResponseDecoder extends ByteToMessageDecoder {
|
|||
* @param out the {@link List} to which decoded messages are added
|
||||
*/
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) {
|
||||
protected void decode(final ChannelHandlerContext context, final ByteBuf in, final List<Object> out) {
|
||||
|
||||
if (RntbdFramer.canDecodeHead(in)) {
|
||||
|
||||
RntbdResponse response = RntbdResponse.decode(in);
|
||||
final RntbdResponse response = RntbdResponse.decode(in);
|
||||
|
||||
if (response != null) {
|
||||
Logger.debug("{} DECODE COMPLETE: {}", context.channel(), response);
|
||||
|
|
|
@ -52,103 +52,103 @@ class RntbdResponseHeaders extends RntbdTokenStream<RntbdResponseHeader> {
|
|||
// region Fields
|
||||
|
||||
@JsonProperty
|
||||
final private RntbdToken LSN;
|
||||
private final RntbdToken LSN;
|
||||
@JsonProperty
|
||||
final private RntbdToken collectionLazyIndexProgress;
|
||||
private final RntbdToken collectionLazyIndexProgress;
|
||||
@JsonProperty
|
||||
final private RntbdToken collectionPartitionIndex;
|
||||
private final RntbdToken collectionPartitionIndex;
|
||||
@JsonProperty
|
||||
final private RntbdToken collectionSecurityIdentifier;
|
||||
private final RntbdToken collectionSecurityIdentifier;
|
||||
@JsonProperty
|
||||
final private RntbdToken collectionServiceIndex;
|
||||
private final RntbdToken collectionServiceIndex;
|
||||
@JsonProperty
|
||||
final private RntbdToken collectionUpdateProgress;
|
||||
private final RntbdToken collectionUpdateProgress;
|
||||
@JsonProperty
|
||||
final private RntbdToken continuationToken;
|
||||
private final RntbdToken continuationToken;
|
||||
@JsonProperty
|
||||
final private RntbdToken currentReplicaSetSize;
|
||||
private final RntbdToken currentReplicaSetSize;
|
||||
@JsonProperty
|
||||
final private RntbdToken currentWriteQuorum;
|
||||
private final RntbdToken currentWriteQuorum;
|
||||
@JsonProperty
|
||||
final private RntbdToken databaseAccountId;
|
||||
private final RntbdToken databaseAccountId;
|
||||
@JsonProperty
|
||||
final private RntbdToken disableRntbdChannel;
|
||||
private final RntbdToken disableRntbdChannel;
|
||||
@JsonProperty
|
||||
final private RntbdToken eTag;
|
||||
private final RntbdToken eTag;
|
||||
@JsonProperty
|
||||
final private RntbdToken globalCommittedLSN;
|
||||
private final RntbdToken globalCommittedLSN;
|
||||
@JsonProperty
|
||||
final private RntbdToken hasTentativeWrites;
|
||||
private final RntbdToken hasTentativeWrites;
|
||||
@JsonProperty
|
||||
final private RntbdToken indexTermsGenerated;
|
||||
private final RntbdToken indexTermsGenerated;
|
||||
@JsonProperty
|
||||
final private RntbdToken indexingDirective;
|
||||
private final RntbdToken indexingDirective;
|
||||
@JsonProperty
|
||||
final private RntbdToken isRUPerMinuteUsed;
|
||||
private final RntbdToken isRUPerMinuteUsed;
|
||||
@JsonProperty
|
||||
final private RntbdToken itemCount;
|
||||
private final RntbdToken itemCount;
|
||||
@JsonProperty
|
||||
final private RntbdToken itemLSN;
|
||||
private final RntbdToken itemLSN;
|
||||
@JsonProperty
|
||||
final private RntbdToken itemLocalLSN;
|
||||
private final RntbdToken itemLocalLSN;
|
||||
@JsonProperty
|
||||
final private RntbdToken lastStateChangeDateTime;
|
||||
private final RntbdToken lastStateChangeDateTime;
|
||||
@JsonProperty
|
||||
final private RntbdToken localLSN;
|
||||
private final RntbdToken localLSN;
|
||||
@JsonProperty
|
||||
final private RntbdToken logResults;
|
||||
private final RntbdToken logResults;
|
||||
@JsonProperty
|
||||
final private RntbdToken numberOfReadRegions;
|
||||
private final RntbdToken numberOfReadRegions;
|
||||
@JsonProperty
|
||||
final private RntbdToken offerReplacePending;
|
||||
private final RntbdToken offerReplacePending;
|
||||
@JsonProperty
|
||||
final private RntbdToken ownerFullName;
|
||||
private final RntbdToken ownerFullName;
|
||||
@JsonProperty
|
||||
final private RntbdToken ownerId;
|
||||
private final RntbdToken ownerId;
|
||||
@JsonProperty
|
||||
final private RntbdToken partitionKeyRangeId;
|
||||
private final RntbdToken partitionKeyRangeId;
|
||||
@JsonProperty
|
||||
final private RntbdToken payloadPresent;
|
||||
private final RntbdToken payloadPresent;
|
||||
@JsonProperty
|
||||
final private RntbdToken queriesPerformed;
|
||||
private final RntbdToken queriesPerformed;
|
||||
@JsonProperty
|
||||
final private RntbdToken queryMetrics;
|
||||
private final RntbdToken queryMetrics;
|
||||
@JsonProperty
|
||||
final private RntbdToken quorumAckedLSN;
|
||||
private final RntbdToken quorumAckedLSN;
|
||||
@JsonProperty
|
||||
final private RntbdToken quorumAckedLocalLSN;
|
||||
private final RntbdToken quorumAckedLocalLSN;
|
||||
@JsonProperty
|
||||
final private RntbdToken readsPerformed;
|
||||
private final RntbdToken readsPerformed;
|
||||
@JsonProperty
|
||||
final private RntbdToken requestCharge;
|
||||
private final RntbdToken requestCharge;
|
||||
@JsonProperty
|
||||
final private RntbdToken requestValidationFailure;
|
||||
private final RntbdToken requestValidationFailure;
|
||||
@JsonProperty
|
||||
final private RntbdToken restoreState;
|
||||
private final RntbdToken restoreState;
|
||||
@JsonProperty
|
||||
final private RntbdToken retryAfterMilliseconds;
|
||||
private final RntbdToken retryAfterMilliseconds;
|
||||
@JsonProperty
|
||||
final private RntbdToken schemaVersion;
|
||||
private final RntbdToken schemaVersion;
|
||||
@JsonProperty
|
||||
final private RntbdToken scriptsExecuted;
|
||||
private final RntbdToken scriptsExecuted;
|
||||
@JsonProperty
|
||||
final private RntbdToken serverDateTimeUtc;
|
||||
private final RntbdToken serverDateTimeUtc;
|
||||
@JsonProperty
|
||||
final private RntbdToken sessionToken;
|
||||
private final RntbdToken sessionToken;
|
||||
@JsonProperty
|
||||
final private RntbdToken shareThroughput;
|
||||
private final RntbdToken shareThroughput;
|
||||
@JsonProperty
|
||||
final private RntbdToken storageMaxResoureQuota;
|
||||
private final RntbdToken storageMaxResoureQuota;
|
||||
@JsonProperty
|
||||
final private RntbdToken storageResourceQuotaUsage;
|
||||
private final RntbdToken storageResourceQuotaUsage;
|
||||
@JsonProperty
|
||||
final private RntbdToken subStatus;
|
||||
private final RntbdToken subStatus;
|
||||
@JsonProperty
|
||||
final private RntbdToken transportRequestID;
|
||||
private final RntbdToken transportRequestID;
|
||||
@JsonProperty
|
||||
final private RntbdToken writesPerformed;
|
||||
private final RntbdToken writesPerformed;
|
||||
@JsonProperty
|
||||
final private RntbdToken xpRole;
|
||||
private final RntbdToken xpRole;
|
||||
|
||||
// endregion
|
||||
|
||||
|
@ -211,13 +211,13 @@ class RntbdResponseHeaders extends RntbdTokenStream<RntbdResponseHeader> {
|
|||
return this.payloadPresent.isPresent() && this.payloadPresent.getValue(Byte.class) != 0x00;
|
||||
}
|
||||
|
||||
List<Map.Entry<String, String>> asList(RntbdContext context, UUID activityId) {
|
||||
List<Map.Entry<String, String>> asList(final RntbdContext context, final UUID activityId) {
|
||||
|
||||
ImmutableList.Builder<Map.Entry<String, String>> builder = ImmutableList.builderWithExpectedSize(this.computeCount() + 2);
|
||||
final ImmutableList.Builder<Map.Entry<String, String>> builder = ImmutableList.builderWithExpectedSize(this.computeCount() + 2);
|
||||
builder.add(new Entry(HttpHeaders.SERVER_VERSION, context.getServerVersion()));
|
||||
builder.add(new Entry(HttpHeaders.ACTIVITY_ID, activityId.toString()));
|
||||
|
||||
collectEntries((token, toEntry) -> {
|
||||
this.collectEntries((token, toEntry) -> {
|
||||
if (token.isPresent()) {
|
||||
builder.add(toEntry.apply(token));
|
||||
}
|
||||
|
@ -226,13 +226,13 @@ class RntbdResponseHeaders extends RntbdTokenStream<RntbdResponseHeader> {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
public Map<String, String> asMap(RntbdContext context, UUID activityId) {
|
||||
public Map<String, String> asMap(final RntbdContext context, final UUID activityId) {
|
||||
|
||||
ImmutableMap.Builder<String, String> builder = ImmutableMap.builderWithExpectedSize(this.computeCount() + 2);
|
||||
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builderWithExpectedSize(this.computeCount() + 2);
|
||||
builder.put(new Entry(HttpHeaders.SERVER_VERSION, context.getServerVersion()));
|
||||
builder.put(new Entry(HttpHeaders.ACTIVITY_ID, activityId.toString()));
|
||||
|
||||
collectEntries((token, toEntry) -> {
|
||||
this.collectEntries((token, toEntry) -> {
|
||||
if (token.isPresent()) {
|
||||
builder.put(toEntry.apply(token));
|
||||
}
|
||||
|
@ -241,80 +241,80 @@ class RntbdResponseHeaders extends RntbdTokenStream<RntbdResponseHeader> {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
static RntbdResponseHeaders decode(ByteBuf in) {
|
||||
RntbdResponseHeaders headers = new RntbdResponseHeaders();
|
||||
static RntbdResponseHeaders decode(final ByteBuf in) {
|
||||
final RntbdResponseHeaders headers = new RntbdResponseHeaders();
|
||||
RntbdTokenStream.decode(in, headers);
|
||||
return headers;
|
||||
}
|
||||
|
||||
public static RntbdResponseHeaders fromMap(Map<String, String> map, boolean payloadPresent) {
|
||||
public static RntbdResponseHeaders fromMap(final Map<String, String> map, final boolean payloadPresent) {
|
||||
|
||||
RntbdResponseHeaders headers = new RntbdResponseHeaders();
|
||||
final RntbdResponseHeaders headers = new RntbdResponseHeaders();
|
||||
headers.payloadPresent.setValue(payloadPresent);
|
||||
headers.setValues(map);
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setValues(Map<String, String> headers) {
|
||||
public void setValues(final Map<String, String> headers) {
|
||||
|
||||
mapValue(this.LSN, BackendHeaders.LSN, Long::parseLong, headers);
|
||||
mapValue(this.collectionLazyIndexProgress, HttpHeaders.COLLECTION_LAZY_INDEXING_PROGRESS, Integer::parseInt, headers);
|
||||
mapValue(this.collectionLazyIndexProgress, BackendHeaders.COLLECTION_PARTITION_INDEX, Integer::parseInt, headers);
|
||||
mapValue(this.collectionSecurityIdentifier, BackendHeaders.COLLECTION_SECURITY_IDENTIFIER, String::toString, headers);
|
||||
mapValue(this.collectionServiceIndex, BackendHeaders.COLLECTION_SERVICE_INDEX, Integer::parseInt, headers);
|
||||
mapValue(this.collectionUpdateProgress, HttpHeaders.COLLECTION_INDEX_TRANSFORMATION_PROGRESS, Integer::parseInt, headers);
|
||||
mapValue(this.continuationToken, HttpHeaders.CONTINUATION, String::toString, headers);
|
||||
mapValue(this.currentReplicaSetSize, BackendHeaders.CURRENT_REPLICA_SET_SIZE, Integer::parseInt, headers);
|
||||
mapValue(this.currentWriteQuorum, BackendHeaders.CURRENT_WRITE_QUORUM, Integer::parseInt, headers);
|
||||
mapValue(this.databaseAccountId, BackendHeaders.DATABASE_ACCOUNT_ID, String::toString, headers);
|
||||
mapValue(this.disableRntbdChannel, HttpHeaders.DISABLE_RNTBD_CHANNEL, Boolean::parseBoolean, headers);
|
||||
mapValue(this.eTag, HttpHeaders.E_TAG, String::toString, headers);
|
||||
mapValue(this.globalCommittedLSN, BackendHeaders.GLOBAL_COMMITTED_LSN, Long::parseLong, headers);
|
||||
mapValue(this.hasTentativeWrites, BackendHeaders.HAS_TENTATIVE_WRITES, Boolean::parseBoolean, headers);
|
||||
mapValue(this.indexingDirective, HttpHeaders.INDEXING_DIRECTIVE, RntbdIndexingDirective::valueOf, headers);
|
||||
mapValue(this.isRUPerMinuteUsed, BackendHeaders.IS_RU_PER_MINUTE_USED, Byte::parseByte, headers);
|
||||
mapValue(this.itemCount, HttpHeaders.ITEM_COUNT, Integer::parseInt, headers);
|
||||
mapValue(this.itemLSN, BackendHeaders.ITEM_LSN, Long::parseLong, headers);
|
||||
mapValue(this.itemLocalLSN, BackendHeaders.ITEM_LOCAL_LSN, Long::parseLong, headers);
|
||||
mapValue(this.lastStateChangeDateTime, HttpHeaders.LAST_STATE_CHANGE_UTC, String::toString, headers);
|
||||
mapValue(this.lastStateChangeDateTime, HttpHeaders.LAST_STATE_CHANGE_UTC, String::toString, headers);
|
||||
mapValue(this.localLSN, BackendHeaders.LOCAL_LSN, Long::parseLong, headers);
|
||||
mapValue(this.logResults, HttpHeaders.LOG_RESULTS, String::toString, headers);
|
||||
mapValue(this.numberOfReadRegions, BackendHeaders.NUMBER_OF_READ_REGIONS, Integer::parseInt, headers);
|
||||
mapValue(this.offerReplacePending, BackendHeaders.OFFER_REPLACE_PENDING, Boolean::parseBoolean, headers);
|
||||
mapValue(this.ownerFullName, HttpHeaders.OWNER_FULL_NAME, String::toString, headers);
|
||||
mapValue(this.ownerId, HttpHeaders.OWNER_ID, String::toString, headers);
|
||||
mapValue(this.partitionKeyRangeId, BackendHeaders.PARTITION_KEY_RANGE_ID, String::toString, headers);
|
||||
mapValue(this.queryMetrics, BackendHeaders.QUERY_METRICS, String::toString, headers);
|
||||
mapValue(this.quorumAckedLSN, BackendHeaders.QUORUM_ACKED_LSN, Long::parseLong, headers);
|
||||
mapValue(this.quorumAckedLocalLSN, BackendHeaders.QUORUM_ACKED_LOCAL_LSN, Long::parseLong, headers);
|
||||
mapValue(this.requestCharge, HttpHeaders.REQUEST_CHARGE, Double::parseDouble, headers);
|
||||
mapValue(this.requestValidationFailure, BackendHeaders.REQUEST_VALIDATION_FAILURE, Byte::parseByte, headers);
|
||||
mapValue(this.restoreState, BackendHeaders.RESTORE_STATE, String::toString, headers);
|
||||
mapValue(this.retryAfterMilliseconds, HttpHeaders.RETRY_AFTER_IN_MILLISECONDS, Integer::parseInt, headers);
|
||||
mapValue(this.schemaVersion, HttpHeaders.SCHEMA_VERSION, String::toString, headers);
|
||||
mapValue(this.serverDateTimeUtc, HttpHeaders.X_DATE, String::toString, headers);
|
||||
mapValue(this.sessionToken, HttpHeaders.SESSION_TOKEN, String::toString, headers);
|
||||
mapValue(this.shareThroughput, BackendHeaders.SHARE_THROUGHPUT, Boolean::parseBoolean, headers);
|
||||
mapValue(this.storageMaxResoureQuota, HttpHeaders.MAX_RESOURCE_QUOTA, String::toString, headers);
|
||||
mapValue(this.storageResourceQuotaUsage, HttpHeaders.CURRENT_RESOURCE_QUOTA_USAGE, String::toString, headers);
|
||||
mapValue(this.subStatus, BackendHeaders.SUB_STATUS, Integer::parseInt, headers);
|
||||
mapValue(this.transportRequestID, HttpHeaders.TRANSPORT_REQUEST_ID, Integer::parseInt, headers);
|
||||
mapValue(this.xpRole, BackendHeaders.XP_ROLE, Integer::parseInt, headers);
|
||||
this.mapValue(this.LSN, BackendHeaders.LSN, Long::parseLong, headers);
|
||||
this.mapValue(this.collectionLazyIndexProgress, HttpHeaders.COLLECTION_LAZY_INDEXING_PROGRESS, Integer::parseInt, headers);
|
||||
this.mapValue(this.collectionLazyIndexProgress, BackendHeaders.COLLECTION_PARTITION_INDEX, Integer::parseInt, headers);
|
||||
this.mapValue(this.collectionSecurityIdentifier, BackendHeaders.COLLECTION_SECURITY_IDENTIFIER, String::toString, headers);
|
||||
this.mapValue(this.collectionServiceIndex, BackendHeaders.COLLECTION_SERVICE_INDEX, Integer::parseInt, headers);
|
||||
this.mapValue(this.collectionUpdateProgress, HttpHeaders.COLLECTION_INDEX_TRANSFORMATION_PROGRESS, Integer::parseInt, headers);
|
||||
this.mapValue(this.continuationToken, HttpHeaders.CONTINUATION, String::toString, headers);
|
||||
this.mapValue(this.currentReplicaSetSize, BackendHeaders.CURRENT_REPLICA_SET_SIZE, Integer::parseInt, headers);
|
||||
this.mapValue(this.currentWriteQuorum, BackendHeaders.CURRENT_WRITE_QUORUM, Integer::parseInt, headers);
|
||||
this.mapValue(this.databaseAccountId, BackendHeaders.DATABASE_ACCOUNT_ID, String::toString, headers);
|
||||
this.mapValue(this.disableRntbdChannel, HttpHeaders.DISABLE_RNTBD_CHANNEL, Boolean::parseBoolean, headers);
|
||||
this.mapValue(this.eTag, HttpHeaders.E_TAG, String::toString, headers);
|
||||
this.mapValue(this.globalCommittedLSN, BackendHeaders.GLOBAL_COMMITTED_LSN, Long::parseLong, headers);
|
||||
this.mapValue(this.hasTentativeWrites, BackendHeaders.HAS_TENTATIVE_WRITES, Boolean::parseBoolean, headers);
|
||||
this.mapValue(this.indexingDirective, HttpHeaders.INDEXING_DIRECTIVE, RntbdIndexingDirective::valueOf, headers);
|
||||
this.mapValue(this.isRUPerMinuteUsed, BackendHeaders.IS_RU_PER_MINUTE_USED, Byte::parseByte, headers);
|
||||
this.mapValue(this.itemCount, HttpHeaders.ITEM_COUNT, Integer::parseInt, headers);
|
||||
this.mapValue(this.itemLSN, BackendHeaders.ITEM_LSN, Long::parseLong, headers);
|
||||
this.mapValue(this.itemLocalLSN, BackendHeaders.ITEM_LOCAL_LSN, Long::parseLong, headers);
|
||||
this.mapValue(this.lastStateChangeDateTime, HttpHeaders.LAST_STATE_CHANGE_UTC, String::toString, headers);
|
||||
this.mapValue(this.lastStateChangeDateTime, HttpHeaders.LAST_STATE_CHANGE_UTC, String::toString, headers);
|
||||
this.mapValue(this.localLSN, BackendHeaders.LOCAL_LSN, Long::parseLong, headers);
|
||||
this.mapValue(this.logResults, HttpHeaders.LOG_RESULTS, String::toString, headers);
|
||||
this.mapValue(this.numberOfReadRegions, BackendHeaders.NUMBER_OF_READ_REGIONS, Integer::parseInt, headers);
|
||||
this.mapValue(this.offerReplacePending, BackendHeaders.OFFER_REPLACE_PENDING, Boolean::parseBoolean, headers);
|
||||
this.mapValue(this.ownerFullName, HttpHeaders.OWNER_FULL_NAME, String::toString, headers);
|
||||
this.mapValue(this.ownerId, HttpHeaders.OWNER_ID, String::toString, headers);
|
||||
this.mapValue(this.partitionKeyRangeId, BackendHeaders.PARTITION_KEY_RANGE_ID, String::toString, headers);
|
||||
this.mapValue(this.queryMetrics, BackendHeaders.QUERY_METRICS, String::toString, headers);
|
||||
this.mapValue(this.quorumAckedLSN, BackendHeaders.QUORUM_ACKED_LSN, Long::parseLong, headers);
|
||||
this.mapValue(this.quorumAckedLocalLSN, BackendHeaders.QUORUM_ACKED_LOCAL_LSN, Long::parseLong, headers);
|
||||
this.mapValue(this.requestCharge, HttpHeaders.REQUEST_CHARGE, Double::parseDouble, headers);
|
||||
this.mapValue(this.requestValidationFailure, BackendHeaders.REQUEST_VALIDATION_FAILURE, Byte::parseByte, headers);
|
||||
this.mapValue(this.restoreState, BackendHeaders.RESTORE_STATE, String::toString, headers);
|
||||
this.mapValue(this.retryAfterMilliseconds, HttpHeaders.RETRY_AFTER_IN_MILLISECONDS, Integer::parseInt, headers);
|
||||
this.mapValue(this.schemaVersion, HttpHeaders.SCHEMA_VERSION, String::toString, headers);
|
||||
this.mapValue(this.serverDateTimeUtc, HttpHeaders.X_DATE, String::toString, headers);
|
||||
this.mapValue(this.sessionToken, HttpHeaders.SESSION_TOKEN, String::toString, headers);
|
||||
this.mapValue(this.shareThroughput, BackendHeaders.SHARE_THROUGHPUT, Boolean::parseBoolean, headers);
|
||||
this.mapValue(this.storageMaxResoureQuota, HttpHeaders.MAX_RESOURCE_QUOTA, String::toString, headers);
|
||||
this.mapValue(this.storageResourceQuotaUsage, HttpHeaders.CURRENT_RESOURCE_QUOTA_USAGE, String::toString, headers);
|
||||
this.mapValue(this.subStatus, BackendHeaders.SUB_STATUS, Integer::parseInt, headers);
|
||||
this.mapValue(this.transportRequestID, HttpHeaders.TRANSPORT_REQUEST_ID, Integer::parseInt, headers);
|
||||
this.mapValue(this.xpRole, BackendHeaders.XP_ROLE, Integer::parseInt, headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
final ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
try {
|
||||
return writer.writeValueAsString(this);
|
||||
} catch (JsonProcessingException error) {
|
||||
} catch (final JsonProcessingException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectEntries(BiConsumer<RntbdToken, Function<RntbdToken, Map.Entry<String, String>>> collector) {
|
||||
private void collectEntries(final BiConsumer<RntbdToken, Function<RntbdToken, Map.Entry<String, String>>> collector) {
|
||||
|
||||
collector.accept(this.LSN, token ->
|
||||
toLongEntry(BackendHeaders.LSN, token)
|
||||
|
@ -461,7 +461,7 @@ class RntbdResponseHeaders extends RntbdTokenStream<RntbdResponseHeader> {
|
|||
);
|
||||
|
||||
collector.accept(this.sessionToken, token ->
|
||||
toSessionTokenEntry(HttpHeaders.SESSION_TOKEN, token)
|
||||
this.toSessionTokenEntry(HttpHeaders.SESSION_TOKEN, token)
|
||||
);
|
||||
|
||||
collector.accept(this.shareThroughput, token ->
|
||||
|
@ -489,46 +489,46 @@ class RntbdResponseHeaders extends RntbdTokenStream<RntbdResponseHeader> {
|
|||
);
|
||||
}
|
||||
|
||||
private void mapValue(RntbdToken token, String name, Function<String, Object> parse, Map<String, String> headers) {
|
||||
private void mapValue(final RntbdToken token, final String name, final Function<String, Object> parse, final Map<String, String> headers) {
|
||||
|
||||
String value = headers.get(name);
|
||||
final String value = headers.get(name);
|
||||
|
||||
if (value != null) {
|
||||
token.setValue(parse.apply(value));
|
||||
}
|
||||
}
|
||||
|
||||
private static Map.Entry<String, String> toBooleanEntry(String name, RntbdToken token) {
|
||||
private static Map.Entry<String, String> toBooleanEntry(final String name, final RntbdToken token) {
|
||||
return new Entry(name, String.valueOf(token.getValue(Byte.class) != 0));
|
||||
}
|
||||
|
||||
private static Map.Entry<String, String> toByteEntry(String name, RntbdToken token) {
|
||||
private static Map.Entry<String, String> toByteEntry(final String name, final RntbdToken token) {
|
||||
return new Entry(name, Byte.toString(token.getValue(Byte.class)));
|
||||
}
|
||||
|
||||
private static Map.Entry<String, String> toCurrencyEntry(String name, RntbdToken token) {
|
||||
BigDecimal value = new BigDecimal(Math.round(token.getValue(Double.class) * 100D)).scaleByPowerOfTen(-2);
|
||||
private static Map.Entry<String, String> toCurrencyEntry(final String name, final RntbdToken token) {
|
||||
final BigDecimal value = new BigDecimal(Math.round(token.getValue(Double.class) * 100D)).scaleByPowerOfTen(-2);
|
||||
return new Entry(name, value.toString());
|
||||
}
|
||||
|
||||
private static Map.Entry<String, String> toIntegerEntry(String name, RntbdToken token) {
|
||||
private static Map.Entry<String, String> toIntegerEntry(final String name, final RntbdToken token) {
|
||||
return new Entry(name, Long.toString(token.getValue(Long.class)));
|
||||
}
|
||||
|
||||
private static Map.Entry<String, String> toLongEntry(String name, RntbdToken token) {
|
||||
private static Map.Entry<String, String> toLongEntry(final String name, final RntbdToken token) {
|
||||
return new Entry(name, Long.toString(token.getValue(Long.class)));
|
||||
}
|
||||
|
||||
private Map.Entry<String, String> toSessionTokenEntry(String name, RntbdToken token) {
|
||||
private Map.Entry<String, String> toSessionTokenEntry(final String name, final RntbdToken token) {
|
||||
return new Entry(name, this.partitionKeyRangeId.getValue(String.class) + ":" + this.sessionToken.getValue(String.class));
|
||||
}
|
||||
|
||||
private static Map.Entry<String, String> toStringEntry(String name, RntbdToken token) {
|
||||
private static Map.Entry<String, String> toStringEntry(final String name, final RntbdToken token) {
|
||||
return new Entry(name, token.getValue(String.class));
|
||||
}
|
||||
|
||||
final private static class Entry extends AbstractMap.SimpleImmutableEntry<String, String> {
|
||||
Entry(String name, String value) {
|
||||
private static final class Entry extends AbstractMap.SimpleImmutableEntry<String, String> {
|
||||
Entry(final String name, final String value) {
|
||||
super(name, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,21 +39,21 @@ final class RntbdResponseStatus {
|
|||
|
||||
// region Fields
|
||||
|
||||
final static int LENGTH = Integer.BYTES // length
|
||||
static final int LENGTH = Integer.BYTES // length
|
||||
+ Integer.BYTES // status
|
||||
+ 2 * Long.BYTES; // activityId
|
||||
|
||||
@JsonProperty("activityId")
|
||||
final private UUID activityId;
|
||||
private final UUID activityId;
|
||||
|
||||
@JsonProperty("length")
|
||||
final private int length;
|
||||
private final int length;
|
||||
|
||||
final private HttpResponseStatus status;
|
||||
private final HttpResponseStatus status;
|
||||
|
||||
// endregion
|
||||
|
||||
RntbdResponseStatus(int length, HttpResponseStatus status, UUID activityId) {
|
||||
RntbdResponseStatus(final int length, final HttpResponseStatus status, final UUID activityId) {
|
||||
this.length = length;
|
||||
this.status = status;
|
||||
this.activityId = activityId;
|
||||
|
@ -80,28 +80,28 @@ final class RntbdResponseStatus {
|
|||
return this.status.code();
|
||||
}
|
||||
|
||||
static RntbdResponseStatus decode(ByteBuf in) {
|
||||
static RntbdResponseStatus decode(final ByteBuf in) {
|
||||
|
||||
long length = in.readUnsignedIntLE();
|
||||
final long length = in.readUnsignedIntLE();
|
||||
|
||||
if (!(LENGTH <= length && length <= Integer.MAX_VALUE)) {
|
||||
String reason = String.format("frame length: %d", length);
|
||||
final String reason = String.format("frame length: %d", length);
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
int code = in.readIntLE();
|
||||
HttpResponseStatus status = HttpResponseStatus.valueOf(code);
|
||||
final int code = in.readIntLE();
|
||||
final HttpResponseStatus status = HttpResponseStatus.valueOf(code);
|
||||
|
||||
if (status == null) {
|
||||
String reason = String.format("status code: %d", code);
|
||||
final String reason = String.format("status code: %d", code);
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
UUID activityId = RntbdUUID.decode(in);
|
||||
final UUID activityId = RntbdUUID.decode(in);
|
||||
return new RntbdResponseStatus((int)length, status, activityId);
|
||||
}
|
||||
|
||||
void encode(ByteBuf out) {
|
||||
void encode(final ByteBuf out) {
|
||||
out.writeIntLE(this.getLength());
|
||||
out.writeIntLE(this.getStatusCode());
|
||||
RntbdUUID.encode(this.getActivityId(), out);
|
||||
|
@ -109,10 +109,10 @@ final class RntbdResponseStatus {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
final ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
try {
|
||||
return writer.writeValueAsString(this);
|
||||
} catch (JsonProcessingException error) {
|
||||
} catch (final JsonProcessingException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2018 Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.microsoft.azure.cosmosdb.BridgeInternal;
|
||||
import com.microsoft.azure.cosmosdb.internal.HttpConstants;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.GoneException;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.RntbdTransportClient.Options;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.StoreResponse;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdReporter.reportIssue;
|
||||
|
||||
@JsonSerialize(using = RntbdServiceEndpoint.JsonSerializer.class)
|
||||
public final class RntbdServiceEndpoint implements RntbdEndpoint {
|
||||
|
||||
private static final AtomicLong instanceCount = new AtomicLong();
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdServiceEndpoint.class);
|
||||
private static final String namePrefix = RntbdServiceEndpoint.class.getSimpleName() + '-';
|
||||
|
||||
private final RntbdClientChannelPool channelPool;
|
||||
private final AtomicBoolean closed;
|
||||
private final RntbdMetrics metrics;
|
||||
private final String name;
|
||||
private final SocketAddress remoteAddress;
|
||||
private final RntbdRequestTimer requestTimer;
|
||||
|
||||
// region Constructors
|
||||
|
||||
private RntbdServiceEndpoint(
|
||||
final Config config, final NioEventLoopGroup group, final RntbdRequestTimer timer, final URI physicalAddress
|
||||
) {
|
||||
|
||||
final Bootstrap bootstrap = new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(group)
|
||||
.option(ChannelOption.AUTO_READ, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeout())
|
||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||
.remoteAddress(physicalAddress.getHost(), physicalAddress.getPort());
|
||||
|
||||
this.name = RntbdServiceEndpoint.namePrefix + instanceCount.incrementAndGet();
|
||||
this.channelPool = new RntbdClientChannelPool(bootstrap, config);
|
||||
this.remoteAddress = bootstrap.config().remoteAddress();
|
||||
this.metrics = new RntbdMetrics(this.name);
|
||||
this.closed = new AtomicBoolean();
|
||||
this.requestTimer = timer;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Accessors
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Methods
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.closed.compareAndSet(false, true)) {
|
||||
this.channelPool.close();
|
||||
this.metrics.close();
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<StoreResponse> request(final RntbdRequestArgs args) {
|
||||
|
||||
this.throwIfClosed();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
args.traceOperation(logger, null, "request");
|
||||
logger.debug("\n {}\n {}\n REQUEST", this, args);
|
||||
}
|
||||
|
||||
final RntbdRequestRecord requestRecord = this.write(args);
|
||||
this.metrics.incrementRequestCount();
|
||||
|
||||
return requestRecord.whenComplete((response, error) -> {
|
||||
|
||||
args.traceOperation(logger, null, "requestComplete", response, error);
|
||||
this.metrics.incrementResponseCount();
|
||||
|
||||
if (error != null) {
|
||||
this.metrics.incrementErrorResponseCount();
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (error == null) {
|
||||
final int status = response.getStatus();
|
||||
logger.debug("\n [{}]\n {}\n request succeeded with response status: {}", this, args, status);
|
||||
} else {
|
||||
logger.debug("\n [{}]\n {}\n request failed due to ", this, args, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return RntbdObjectMapper.toJson(this);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Privates
|
||||
|
||||
private void releaseToPool(final Channel channel) {
|
||||
|
||||
logger.debug("\n [{}]\n {}\n RELEASE", this, channel);
|
||||
|
||||
this.channelPool.release(channel).addListener(future -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (future.isSuccess()) {
|
||||
logger.debug("\n [{}]\n {}\n release succeeded", this, channel);
|
||||
} else {
|
||||
logger.debug("\n [{}]\n {}\n release failed due to {}", this, channel, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void throwIfClosed() {
|
||||
checkState(!this.closed.get(), "%s is closed", this);
|
||||
}
|
||||
|
||||
private RntbdRequestRecord write(final RntbdRequestArgs requestArgs) {
|
||||
|
||||
final RntbdRequestRecord requestRecord = new RntbdRequestRecord(requestArgs, this.requestTimer);
|
||||
logger.debug("\n [{}]\n {}\n WRITE", this, requestArgs);
|
||||
|
||||
this.channelPool.acquire().addListener(connected -> {
|
||||
|
||||
if (connected.isSuccess()) {
|
||||
|
||||
requestArgs.traceOperation(logger, null, "write");
|
||||
final Channel channel = (Channel)connected.get();
|
||||
this.releaseToPool(channel);
|
||||
|
||||
channel.write(requestRecord).addListener((ChannelFuture future) -> {
|
||||
requestArgs.traceOperation(logger, null, "writeComplete", channel);
|
||||
if (!future.isSuccess()) {
|
||||
this.metrics.incrementErrorResponseCount();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID activityId = requestArgs.getActivityId();
|
||||
final Throwable cause = connected.cause();
|
||||
|
||||
if (connected.isCancelled()) {
|
||||
|
||||
logger.debug("\n [{}]\n {}\n write cancelled: {}", this, requestArgs, cause);
|
||||
requestRecord.cancel(true);
|
||||
|
||||
} else {
|
||||
|
||||
logger.debug("\n [{}]\n {}\n write failed due to {} ", this, requestArgs, cause);
|
||||
final String reason = cause.getMessage();
|
||||
|
||||
final GoneException goneException = new GoneException(
|
||||
String.format("failed to establish connection to %s: %s", this.remoteAddress, reason),
|
||||
cause instanceof Exception ? (Exception)cause : new IOException(reason, cause),
|
||||
ImmutableMap.of(HttpConstants.HttpHeaders.ACTIVITY_ID, activityId.toString()),
|
||||
requestArgs.getReplicaPath()
|
||||
);
|
||||
|
||||
BridgeInternal.setRequestHeaders(goneException, requestArgs.getServiceRequest().getHeaders());
|
||||
requestRecord.completeExceptionally(goneException);
|
||||
}
|
||||
});
|
||||
|
||||
return requestRecord;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Types
|
||||
|
||||
static final class JsonSerializer extends StdSerializer<RntbdServiceEndpoint> {
|
||||
|
||||
public JsonSerializer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public JsonSerializer(Class<RntbdServiceEndpoint> type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(RntbdServiceEndpoint value, JsonGenerator generator, SerializerProvider provider)
|
||||
throws IOException {
|
||||
|
||||
generator.writeStartObject();
|
||||
generator.writeStringField(value.name, value.remoteAddress.toString());
|
||||
generator.writeObjectField("channelPool", value.channelPool);
|
||||
generator.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Provider implements RntbdEndpoint.Provider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Provider.class);
|
||||
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
private final Config config;
|
||||
private final ConcurrentHashMap<String, RntbdEndpoint> endpoints = new ConcurrentHashMap<>();
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final RntbdRequestTimer requestTimer;
|
||||
|
||||
public Provider(final Options options, final SslContext sslContext) {
|
||||
|
||||
checkNotNull(options, "options");
|
||||
checkNotNull(sslContext, "sslContext");
|
||||
|
||||
final DefaultThreadFactory threadFactory = new DefaultThreadFactory("CosmosEventLoop", true);
|
||||
final int threadCount = Runtime.getRuntime().availableProcessors();
|
||||
final LogLevel wireLogLevel;
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
wireLogLevel = LogLevel.TRACE;
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
wireLogLevel = LogLevel.DEBUG;
|
||||
} else {
|
||||
wireLogLevel = null;
|
||||
}
|
||||
|
||||
this.config = new Config(options, sslContext, wireLogLevel);
|
||||
this.requestTimer = new RntbdRequestTimer(config.getRequestTimeout());
|
||||
this.eventLoopGroup = new NioEventLoopGroup(threadCount, threadFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws RuntimeException {
|
||||
|
||||
if (this.closed.compareAndSet(false, true)) {
|
||||
|
||||
this.requestTimer.close();
|
||||
|
||||
for (final RntbdEndpoint endpoint : this.endpoints.values()) {
|
||||
endpoint.close();
|
||||
}
|
||||
|
||||
this.eventLoopGroup.shutdownGracefully().addListener(future -> {
|
||||
if (future.isSuccess()) {
|
||||
logger.debug("\n [{}]\n closed endpoints", this);
|
||||
return;
|
||||
}
|
||||
logger.error("\n [{}]\n failed to close endpoints due to ", this, future.cause());
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("\n [{}]\n already closed", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config config() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return this.endpoints.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RntbdEndpoint get(URI physicalAddress) {
|
||||
return endpoints.computeIfAbsent(physicalAddress.getAuthority(), authority ->
|
||||
new RntbdServiceEndpoint(config, eventLoopGroup, requestTimer, physicalAddress)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RntbdEndpoint> list() {
|
||||
return this.endpoints.values().stream();
|
||||
}
|
||||
|
||||
private void deleteEndpoint(final URI physicalAddress) {
|
||||
|
||||
// TODO: DANOBLE: Utilize this method of tearing down unhealthy endpoints
|
||||
// Specifically, ensure that this method is called when a Read/WriteTimeoutException occurs or a health
|
||||
// check request fails. This perhaps likely requires a change to RntbdClientChannelPool.
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/331552
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/331593
|
||||
|
||||
checkNotNull(physicalAddress, "physicalAddress: %s", physicalAddress);
|
||||
|
||||
final String authority = physicalAddress.getAuthority();
|
||||
final RntbdEndpoint endpoint = this.endpoints.remove(authority);
|
||||
|
||||
if (endpoint != null) {
|
||||
endpoint.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
|
@ -35,19 +35,18 @@ import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
|
|||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.RntbdHeader;
|
||||
|
||||
@JsonPropertyOrder({ "id", "name", "type", "present", "required", "value" })
|
||||
class RntbdToken {
|
||||
final class RntbdToken {
|
||||
|
||||
// region Fields
|
||||
|
||||
private static final int HEADER_LENGTH = Short.BYTES + Byte.BYTES;
|
||||
|
||||
static {
|
||||
RntbdObjectMapper.registerPropertyFilter(RntbdToken.class, PropertyFilter.class);
|
||||
RntbdObjectMapper.registerPropertyFilter(RntbdToken.class, RntbdToken.PropertyFilter.class);
|
||||
}
|
||||
|
||||
private final RntbdHeader header;
|
||||
|
@ -56,28 +55,28 @@ class RntbdToken {
|
|||
|
||||
// endregion
|
||||
|
||||
private RntbdToken(RntbdHeader header) {
|
||||
Objects.requireNonNull(header);
|
||||
private RntbdToken(final RntbdHeader header) {
|
||||
checkNotNull(header, "header");
|
||||
this.header = header;
|
||||
this.value = null;
|
||||
this.length = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
RntbdTokenType getType() {
|
||||
return this.header.type();
|
||||
final short getId() {
|
||||
return this.header.id();
|
||||
}
|
||||
|
||||
// region Accessors
|
||||
|
||||
@JsonProperty
|
||||
final short getId() {
|
||||
return this.header.id();
|
||||
final String getName() {
|
||||
return this.header.name();
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
final String getName() {
|
||||
return this.header.name();
|
||||
final RntbdTokenType getType() {
|
||||
return this.header.type();
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
|
@ -88,7 +87,7 @@ class RntbdToken {
|
|||
}
|
||||
|
||||
if (this.value instanceof ByteBuf) {
|
||||
ByteBuf buffer = (ByteBuf)this.value;
|
||||
final ByteBuf buffer = (ByteBuf)this.value;
|
||||
this.value = this.header.type().codec().read(buffer);
|
||||
buffer.release();
|
||||
} else {
|
||||
|
@ -98,27 +97,8 @@ class RntbdToken {
|
|||
return this.value;
|
||||
}
|
||||
|
||||
final int computeLength() {
|
||||
|
||||
if (!this.isPresent()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.value instanceof ByteBuf) {
|
||||
ByteBuf buffer = (ByteBuf)this.value;
|
||||
assert buffer.readerIndex() == 0;
|
||||
return HEADER_LENGTH + buffer.readableBytes();
|
||||
}
|
||||
|
||||
if (this.length == Integer.MIN_VALUE) {
|
||||
this.length = HEADER_LENGTH + this.header.type().codec().computeLength(this.value);
|
||||
}
|
||||
|
||||
return this.length;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
final void setValue(Object value) {
|
||||
final void setValue(final Object value) {
|
||||
this.ensureValid(value);
|
||||
this.length = Integer.MIN_VALUE;
|
||||
this.value = value;
|
||||
|
@ -134,17 +114,36 @@ class RntbdToken {
|
|||
return this.header.isRequired();
|
||||
}
|
||||
|
||||
final int computeLength() {
|
||||
|
||||
if (!this.isPresent()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.value instanceof ByteBuf) {
|
||||
final ByteBuf buffer = (ByteBuf)this.value;
|
||||
assert buffer.readerIndex() == 0;
|
||||
return HEADER_LENGTH + buffer.readableBytes();
|
||||
}
|
||||
|
||||
if (this.length == Integer.MIN_VALUE) {
|
||||
this.length = HEADER_LENGTH + this.header.type().codec().computeLength(this.value);
|
||||
}
|
||||
|
||||
return this.length;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Methods
|
||||
|
||||
static RntbdToken create(RntbdHeader header) {
|
||||
static RntbdToken create(final RntbdHeader header) {
|
||||
return new RntbdToken(header);
|
||||
}
|
||||
|
||||
void decode(ByteBuf in) {
|
||||
void decode(final ByteBuf in) {
|
||||
|
||||
Objects.requireNonNull(in);
|
||||
checkNotNull(in, "in");
|
||||
|
||||
if (this.value instanceof ByteBuf) {
|
||||
((ByteBuf)this.value).release();
|
||||
|
@ -153,13 +152,13 @@ class RntbdToken {
|
|||
this.value = this.header.type().codec().readSlice(in).retain(); // No data transfer until the first call to RntbdToken.getValue
|
||||
}
|
||||
|
||||
final void encode(ByteBuf out) {
|
||||
final void encode(final ByteBuf out) {
|
||||
|
||||
Objects.requireNonNull(out);
|
||||
checkNotNull(out, "out");
|
||||
|
||||
if (!this.isPresent()) {
|
||||
if (this.isRequired()) {
|
||||
String message = String.format("Missing value for required header: %s", this);
|
||||
final String message = String.format("Missing value for required header: %s", this);
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
return;
|
||||
|
@ -171,38 +170,38 @@ class RntbdToken {
|
|||
if (this.value instanceof ByteBuf) {
|
||||
out.writeBytes((ByteBuf)this.value);
|
||||
} else {
|
||||
this.ensureValid(value);
|
||||
this.header.type().codec().write(value, out);
|
||||
this.ensureValid(this.value);
|
||||
this.header.type().codec().write(this.value, out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
try {
|
||||
return writer.writeValueAsString(this);
|
||||
} catch (JsonProcessingException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
}
|
||||
|
||||
final <T> T getValue(Class<T> cls) {
|
||||
final <T> T getValue(final Class<T> cls) {
|
||||
return cls.cast(this.getValue());
|
||||
}
|
||||
|
||||
final void releaseBuffer() {
|
||||
if (this.value instanceof ByteBuf) {
|
||||
ByteBuf buffer = (ByteBuf)this.value;
|
||||
final ByteBuf buffer = (ByteBuf)this.value;
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureValid(Object value) {
|
||||
@Override
|
||||
public String toString() {
|
||||
final ObjectWriter writer = RntbdObjectMapper.writer();
|
||||
try {
|
||||
return writer.writeValueAsString(this);
|
||||
} catch (final JsonProcessingException error) {
|
||||
throw new CorruptedFrameException(error);
|
||||
}
|
||||
}
|
||||
|
||||
Objects.requireNonNull(value);
|
||||
private void ensureValid(final Object value) {
|
||||
|
||||
checkNotNull(value, "value");
|
||||
|
||||
if (!this.header.type().codec().isValid(value)) {
|
||||
String reason = String.format("value: %s", value.getClass());
|
||||
final String reason = String.format("value: %s", value.getClass());
|
||||
throw new IllegalArgumentException(reason);
|
||||
}
|
||||
}
|
||||
|
@ -214,11 +213,11 @@ class RntbdToken {
|
|||
static class PropertyFilter extends SimpleBeanPropertyFilter {
|
||||
|
||||
@Override
|
||||
public void serializeAsField(Object object, JsonGenerator generator, SerializerProvider provider, PropertyWriter writer) throws Exception {
|
||||
public void serializeAsField(final Object object, final JsonGenerator generator, final SerializerProvider provider, final PropertyWriter writer) throws Exception {
|
||||
|
||||
if (generator.canOmitFields()) {
|
||||
|
||||
Object value = writer.getMember().getValue(object);
|
||||
final Object value = writer.getMember().getValue(object);
|
||||
|
||||
if (value instanceof RntbdToken && !((RntbdToken)value).isPresent()) {
|
||||
return;
|
||||
|
|
|
@ -29,9 +29,9 @@ import com.google.common.collect.ImmutableSet;
|
|||
import com.google.common.collect.Maps;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collector;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdConstants.RntbdHeader;
|
||||
|
||||
abstract class RntbdTokenStream<T extends Enum<T> & RntbdHeader> {
|
||||
|
@ -39,12 +39,12 @@ abstract class RntbdTokenStream<T extends Enum<T> & RntbdHeader> {
|
|||
final ImmutableMap<Short, T> headers;
|
||||
final ImmutableMap<T, RntbdToken> tokens;
|
||||
|
||||
RntbdTokenStream(ImmutableSet<T> headers, ImmutableMap<Short, T> ids) {
|
||||
RntbdTokenStream(final ImmutableSet<T> headers, final ImmutableMap<Short, T> ids) {
|
||||
|
||||
Objects.requireNonNull(headers, "headers");
|
||||
Objects.requireNonNull(ids, "ids");
|
||||
checkNotNull(headers, "headers");
|
||||
checkNotNull(ids, "ids");
|
||||
|
||||
Collector<T, ?, ImmutableMap<T, RntbdToken>> collector = Maps.toImmutableEnumMap(h -> h, RntbdToken::create);
|
||||
final Collector<T, ?, ImmutableMap<T, RntbdToken>> collector = Maps.toImmutableEnumMap(h -> h, RntbdToken::create);
|
||||
this.tokens = headers.stream().collect(collector);
|
||||
this.headers = ids;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ abstract class RntbdTokenStream<T extends Enum<T> & RntbdHeader> {
|
|||
|
||||
int count = 0;
|
||||
|
||||
for (RntbdToken token : this.tokens.values()) {
|
||||
for (final RntbdToken token : this.tokens.values()) {
|
||||
if (token.isPresent()) {
|
||||
++count;
|
||||
}
|
||||
|
@ -66,14 +66,14 @@ abstract class RntbdTokenStream<T extends Enum<T> & RntbdHeader> {
|
|||
|
||||
int total = 0;
|
||||
|
||||
for (RntbdToken token : this.tokens.values()) {
|
||||
for (final RntbdToken token : this.tokens.values()) {
|
||||
total += token.computeLength();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static <T extends RntbdTokenStream<?>> T decode(ByteBuf in, T stream) {
|
||||
static <T extends RntbdTokenStream<?>> T decode(final ByteBuf in, final T stream) {
|
||||
|
||||
while (in.readableBytes() > 0) {
|
||||
|
||||
|
@ -89,9 +89,9 @@ abstract class RntbdTokenStream<T extends Enum<T> & RntbdHeader> {
|
|||
token.decode(in);
|
||||
}
|
||||
|
||||
for (RntbdToken token : stream.tokens.values()) {
|
||||
for (final RntbdToken token : stream.tokens.values()) {
|
||||
if (!token.isPresent() && token.isRequired()) {
|
||||
String reason = String.format("Required token not found on RNTBD stream: type: %s, identifier: %s",
|
||||
final String reason = String.format("Required token not found on RNTBD stream: type: %s, identifier: %s",
|
||||
token.getType(), token.getId());
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
|
@ -100,28 +100,28 @@ abstract class RntbdTokenStream<T extends Enum<T> & RntbdHeader> {
|
|||
return stream;
|
||||
}
|
||||
|
||||
final void encode(ByteBuf out) {
|
||||
for (RntbdToken token : this.tokens.values()) {
|
||||
final void encode(final ByteBuf out) {
|
||||
for (final RntbdToken token : this.tokens.values()) {
|
||||
token.encode(out);
|
||||
}
|
||||
}
|
||||
|
||||
final RntbdToken get(T header) {
|
||||
final RntbdToken get(final T header) {
|
||||
return this.tokens.get(header);
|
||||
}
|
||||
|
||||
final void releaseBuffers() {
|
||||
for (RntbdToken token : this.tokens.values()) {
|
||||
for (final RntbdToken token : this.tokens.values()) {
|
||||
token.releaseBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
final static private class UndefinedHeader implements RntbdHeader {
|
||||
private static final class UndefinedHeader implements RntbdHeader {
|
||||
|
||||
final private short id;
|
||||
final private RntbdTokenType type;
|
||||
private final short id;
|
||||
private final RntbdTokenType type;
|
||||
|
||||
UndefinedHeader(short id, RntbdTokenType type) {
|
||||
UndefinedHeader(final short id, final RntbdTokenType type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
|
|
@ -64,14 +64,18 @@ enum RntbdTokenType {
|
|||
private Codec codec;
|
||||
private byte id;
|
||||
|
||||
RntbdTokenType(byte id, Codec codec) {
|
||||
RntbdTokenType(final byte id, final Codec codec) {
|
||||
this.codec = codec;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static RntbdTokenType fromId(byte value) {
|
||||
public Codec codec() {
|
||||
return this.codec;
|
||||
}
|
||||
|
||||
for (RntbdTokenType tokenType : RntbdTokenType.values()) {
|
||||
public static RntbdTokenType fromId(final byte value) {
|
||||
|
||||
for (final RntbdTokenType tokenType : RntbdTokenType.values()) {
|
||||
if (value == tokenType.id) {
|
||||
return tokenType;
|
||||
}
|
||||
|
@ -79,10 +83,6 @@ enum RntbdTokenType {
|
|||
return Invalid;
|
||||
}
|
||||
|
||||
public Codec codec() {
|
||||
return this.codec;
|
||||
}
|
||||
|
||||
public byte id() {
|
||||
return this.id;
|
||||
}
|
||||
|
@ -116,14 +116,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
public final int computeLength(final Object value) {
|
||||
return java.lang.Byte.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
public final Object convert(final Object value) {
|
||||
|
||||
assert isValid(value);
|
||||
assert this.isValid(value);
|
||||
|
||||
if (value instanceof Number) {
|
||||
return ((Number)value).byteValue();
|
||||
|
@ -137,23 +137,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof Number || value instanceof Boolean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(java.lang.Byte.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
out.writeByte(value instanceof Byte ? (byte)value : ((boolean)value ? 0x01 : 0x00));
|
||||
}
|
||||
}
|
||||
|
@ -167,14 +167,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return Short.BYTES + ((byte[])value).length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -184,29 +184,29 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Object value) {
|
||||
public boolean isValid(final Object value) {
|
||||
return value instanceof byte[] && ((byte[])value).length < 0xFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(ByteBuf in) {
|
||||
int length = in.readUnsignedShortLE();
|
||||
public Object read(final ByteBuf in) {
|
||||
final int length = in.readUnsignedShortLE();
|
||||
return in.readBytes(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(ByteBuf in) {
|
||||
int length = in.getUnsignedShortLE(in.readerIndex());
|
||||
public ByteBuf readSlice(final ByteBuf in) {
|
||||
final int length = in.getUnsignedShortLE(in.readerIndex());
|
||||
return in.readSlice(Short.BYTES + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, ByteBuf out) {
|
||||
public void write(final Object value, final ByteBuf out) {
|
||||
|
||||
assert isValid(value);
|
||||
assert this.isValid(value);
|
||||
|
||||
byte[] bytes = (byte[])value;
|
||||
int length = bytes.length;
|
||||
final byte[] bytes = (byte[])value;
|
||||
final int length = bytes.length;
|
||||
|
||||
if (length > 0xFFFF) {
|
||||
throw new IllegalStateException();
|
||||
|
@ -225,14 +225,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return java.lang.Double.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return ((Number)value).doubleValue();
|
||||
}
|
||||
|
||||
|
@ -242,23 +242,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof Number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readDoubleLE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(java.lang.Double.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
out.writeDoubleLE(((Number)value).doubleValue());
|
||||
}
|
||||
}
|
||||
|
@ -271,14 +271,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return java.lang.Float.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return ((Number)value).floatValue();
|
||||
}
|
||||
|
||||
|
@ -288,23 +288,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof Number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readFloatLE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(java.lang.Float.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
out.writeFloatLE(((Number)value).floatValue());
|
||||
}
|
||||
}
|
||||
|
@ -317,14 +317,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return 2 * java.lang.Long.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -334,23 +334,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return RntbdUUID.decode(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(2 * java.lang.Long.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
RntbdUUID.encode((UUID)value, out);
|
||||
}
|
||||
}
|
||||
|
@ -363,14 +363,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return Integer.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return ((Number)value).intValue();
|
||||
}
|
||||
|
||||
|
@ -380,23 +380,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof Number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readIntLE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(Integer.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
out.writeIntLE(((Number)value).intValue());
|
||||
}
|
||||
}
|
||||
|
@ -409,14 +409,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return java.lang.Long.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return ((Number)value).longValue();
|
||||
}
|
||||
|
||||
|
@ -426,23 +426,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof Number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readLongLE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(java.lang.Long.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
out.writeLongLE(((Number)value).longValue());
|
||||
}
|
||||
}
|
||||
|
@ -455,20 +455,20 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return Integer.BYTES + ((byte[])value).length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof byte[] && ((byte[])value).length < 0xFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
|
||||
long length = in.readUnsignedIntLE();
|
||||
final long length = in.readUnsignedIntLE();
|
||||
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
throw new IllegalStateException();
|
||||
|
@ -477,9 +477,9 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
|
||||
long length = in.getUnsignedIntLE(in.readerIndex());
|
||||
final long length = in.getUnsignedIntLE(in.readerIndex());
|
||||
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
throw new IllegalStateException();
|
||||
|
@ -488,11 +488,11 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
|
||||
assert isValid(value);
|
||||
assert this.isValid(value);
|
||||
|
||||
byte[] bytes = (byte[])value;
|
||||
final byte[] bytes = (byte[])value;
|
||||
out.writeIntLE(bytes.length);
|
||||
out.writeBytes(bytes);
|
||||
}
|
||||
|
@ -506,14 +506,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
public final int computeLength(final Object value) {
|
||||
return Integer.BYTES + this.computeLength(value, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
|
||||
long length = in.readUnsignedIntLE();
|
||||
final long length = in.readUnsignedIntLE();
|
||||
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
throw new IllegalStateException();
|
||||
|
@ -523,9 +523,9 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
|
||||
int length = this.computeLength(value, Integer.MAX_VALUE);
|
||||
final int length = this.computeLength(value, Integer.MAX_VALUE);
|
||||
out.writeIntLE(length);
|
||||
writeValue(out, value, length);
|
||||
}
|
||||
|
@ -536,12 +536,12 @@ enum RntbdTokenType {
|
|||
public static final Codec codec = new RntbdNone();
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
public final int computeLength(final Object value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
public final Object convert(final Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -551,22 +551,22 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -578,38 +578,38 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return java.lang.Byte.BYTES + ((byte[])value).length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof byte[] && ((byte[])value).length < 0xFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
|
||||
int length = in.readUnsignedByte();
|
||||
byte[] bytes = new byte[length];
|
||||
final int length = in.readUnsignedByte();
|
||||
final byte[] bytes = new byte[length];
|
||||
in.readBytes(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(java.lang.Byte.BYTES + in.getUnsignedByte(in.readerIndex()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
|
||||
assert isValid(value);
|
||||
assert this.isValid(value);
|
||||
|
||||
byte[] bytes = (byte[])value;
|
||||
int length = bytes.length;
|
||||
final byte[] bytes = (byte[])value;
|
||||
final int length = bytes.length;
|
||||
|
||||
if (length > 0xFF) {
|
||||
throw new IllegalStateException();
|
||||
|
@ -628,24 +628,24 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
public final int computeLength(final Object value) {
|
||||
return java.lang.Byte.BYTES + this.computeLength(value, 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readCharSequence(in.readUnsignedByte(), StandardCharsets.UTF_8).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(java.lang.Byte.BYTES + in.getUnsignedByte(in.readerIndex()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
|
||||
int length = this.computeLength(value, 0xFF);
|
||||
final int length = this.computeLength(value, 0xFF);
|
||||
out.writeByte(length);
|
||||
writeValue(out, value, length);
|
||||
}
|
||||
|
@ -658,22 +658,22 @@ enum RntbdTokenType {
|
|||
private RntbdString() {
|
||||
}
|
||||
|
||||
final int computeLength(Object value, int maxLength) {
|
||||
final int computeLength(final Object value, final int maxLength) {
|
||||
|
||||
assert isValid(value);
|
||||
int length;
|
||||
assert this.isValid(value);
|
||||
final int length;
|
||||
|
||||
if (value instanceof String) {
|
||||
|
||||
String string = (String)value;
|
||||
final String string = (String)value;
|
||||
length = Utf8.encodedLength(string);
|
||||
|
||||
} else {
|
||||
|
||||
byte[] string = (byte[])value;
|
||||
final byte[] string = (byte[])value;
|
||||
|
||||
if (!Utf8.isWellFormed(string)) {
|
||||
String reason = java.lang.String.format("UTF-8 byte string is ill-formed: %s", ByteBufUtil.hexDump(string));
|
||||
final String reason = java.lang.String.format("UTF-8 byte string is ill-formed: %s", ByteBufUtil.hexDump(string));
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
|
@ -681,7 +681,7 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
if (length > maxLength) {
|
||||
String reason = java.lang.String.format("UTF-8 byte string exceeds %d bytes: %d bytes", maxLength, length);
|
||||
final String reason = java.lang.String.format("UTF-8 byte string exceeds %d bytes: %d bytes", maxLength, length);
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
|
@ -689,13 +689,13 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int computeLength(Object value) {
|
||||
public int computeLength(final Object value) {
|
||||
return Short.BYTES + this.computeLength(value, 0xFFFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return value instanceof String ? value : new String((byte[])value, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
|
@ -705,32 +705,32 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof String || value instanceof byte[];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(ByteBuf in) {
|
||||
int length = in.readUnsignedShortLE();
|
||||
public Object read(final ByteBuf in) {
|
||||
final int length = in.readUnsignedShortLE();
|
||||
return in.readCharSequence(length, StandardCharsets.UTF_8).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readSlice(ByteBuf in) {
|
||||
public ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(Short.BYTES + in.getUnsignedShortLE(in.readerIndex()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, ByteBuf out) {
|
||||
public void write(final Object value, final ByteBuf out) {
|
||||
|
||||
int length = this.computeLength(value, 0xFFFF);
|
||||
final int length = this.computeLength(value, 0xFFFF);
|
||||
out.writeShortLE(length);
|
||||
writeValue(out, value, length);
|
||||
}
|
||||
|
||||
static void writeValue(ByteBuf out, Object value, int length) {
|
||||
static void writeValue(final ByteBuf out, final Object value, final int length) {
|
||||
|
||||
int start = out.writerIndex();
|
||||
final int start = out.writerIndex();
|
||||
|
||||
if (value instanceof String) {
|
||||
out.writeCharSequence((String)value, StandardCharsets.UTF_8);
|
||||
|
@ -750,14 +750,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return Integer.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return ((Number)value).longValue() & 0xFFFFFFFFL;
|
||||
}
|
||||
|
||||
|
@ -767,23 +767,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof Number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readUnsignedIntLE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(Integer.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
out.writeIntLE(((Number)value).intValue());
|
||||
}
|
||||
}
|
||||
|
@ -796,14 +796,14 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final int computeLength(Object value) {
|
||||
assert isValid(value);
|
||||
public final int computeLength(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return Short.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object convert(Object value) {
|
||||
assert isValid(value);
|
||||
public final Object convert(final Object value) {
|
||||
assert this.isValid(value);
|
||||
return ((Number)value).intValue() & 0xFFFF;
|
||||
}
|
||||
|
||||
|
@ -813,23 +813,23 @@ enum RntbdTokenType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final boolean isValid(Object value) {
|
||||
public final boolean isValid(final Object value) {
|
||||
return value instanceof Number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object read(ByteBuf in) {
|
||||
public final Object read(final ByteBuf in) {
|
||||
return in.readUnsignedShortLE();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ByteBuf readSlice(ByteBuf in) {
|
||||
public final ByteBuf readSlice(final ByteBuf in) {
|
||||
return in.readSlice(Short.BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(Object value, ByteBuf out) {
|
||||
assert isValid(value);
|
||||
public final void write(final Object value, final ByteBuf out) {
|
||||
assert this.isValid(value);
|
||||
out.writeShortLE(((Number)value).shortValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,13 @@ import io.netty.buffer.ByteBuf;
|
|||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
final public class RntbdUUID {
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
final public static UUID EMPTY = new UUID(0L, 0L);
|
||||
public final class RntbdUUID {
|
||||
|
||||
public static final UUID EMPTY = new UUID(0L, 0L);
|
||||
|
||||
private RntbdUUID() {
|
||||
}
|
||||
|
@ -44,7 +45,7 @@ final public class RntbdUUID {
|
|||
* @param bytes a {@link byte} array containing the serialized {@link UUID} to be decoded
|
||||
* @return a new {@link UUID}
|
||||
*/
|
||||
public static UUID decode(byte[] bytes) {
|
||||
public static UUID decode(final byte[] bytes) {
|
||||
return decode(Unpooled.wrappedBuffer(bytes));
|
||||
}
|
||||
|
||||
|
@ -54,12 +55,12 @@ final public class RntbdUUID {
|
|||
* @param in a {@link ByteBuf} containing the serialized {@link UUID} to be decoded
|
||||
* @return a new {@link UUID}
|
||||
*/
|
||||
public static UUID decode(ByteBuf in) {
|
||||
public static UUID decode(final ByteBuf in) {
|
||||
|
||||
Objects.requireNonNull(in);
|
||||
checkNotNull(in, "in");
|
||||
|
||||
if (in.readableBytes() < 2 * Long.BYTES) {
|
||||
String reason = String.format("invalid frame length: %d", in.readableBytes());
|
||||
final String reason = String.format("invalid frame length: %d", in.readableBytes());
|
||||
throw new CorruptedFrameException(reason);
|
||||
}
|
||||
|
||||
|
@ -83,7 +84,7 @@ final public class RntbdUUID {
|
|||
* @param uuid a {@link UUID} to be encoded
|
||||
* @return a new byte array containing the encoded
|
||||
*/
|
||||
public static byte[] encode(UUID uuid) {
|
||||
public static byte[] encode(final UUID uuid) {
|
||||
final byte[] bytes = new byte[2 * Integer.BYTES];
|
||||
encode(uuid, Unpooled.wrappedBuffer(bytes));
|
||||
return bytes;
|
||||
|
@ -95,7 +96,7 @@ final public class RntbdUUID {
|
|||
* @param uuid a {@link UUID} to be encoded
|
||||
* @param out an output {@link ByteBuf}
|
||||
*/
|
||||
public static void encode(UUID uuid, ByteBuf out) {
|
||||
public static void encode(final UUID uuid, final ByteBuf out) {
|
||||
|
||||
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||
|
||||
|
|
|
@ -36,9 +36,12 @@ import com.microsoft.azure.cosmosdb.internal.Utils;
|
|||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdContext;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdContextNegotiator;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdContextRequest;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdEndpoint;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestArgs;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestEncoder;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestManager;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestRecord;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdRequestTimer;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdResponse;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdResponseDecoder;
|
||||
import com.microsoft.azure.cosmosdb.internal.directconnectivity.rntbd.RntbdUUID;
|
||||
|
@ -54,13 +57,11 @@ import io.netty.buffer.Unpooled;
|
|||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.util.concurrent.DefaultEventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testng.annotations.DataProvider;
|
||||
|
@ -75,32 +76,28 @@ import java.time.Duration;
|
|||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.microsoft.azure.cosmosdb.internal.HttpConstants.HttpHeaders;
|
||||
import static com.microsoft.azure.cosmosdb.internal.HttpConstants.HttpMethods;
|
||||
import static com.microsoft.azure.cosmosdb.internal.HttpConstants.SubStatusCodes;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
public class RntbdTransportClientTest {
|
||||
public final class RntbdTransportClientTest {
|
||||
|
||||
final private static Logger logger = LoggerFactory.getLogger(RntbdTransportClientTest.class);
|
||||
final private static int lsn = 5;
|
||||
final private static ByteBuf noContent = Unpooled.wrappedBuffer(new byte[0]);
|
||||
final private static String partitionKeyRangeId = "3";
|
||||
final private static URI physicalAddress = URI.create("rntbd://host:10251/replica-path/");
|
||||
final private static Duration requestTimeout = Duration.ofSeconds(1000);
|
||||
private static final Logger logger = LoggerFactory.getLogger(RntbdTransportClientTest.class);
|
||||
private static final int lsn = 5;
|
||||
private static final ByteBuf noContent = Unpooled.wrappedBuffer(new byte[0]);
|
||||
private static final String partitionKeyRangeId = "3";
|
||||
private static final URI physicalAddress = URI.create("rntbd://host:10251/replica-path/");
|
||||
private static final Duration requestTimeout = Duration.ofSeconds(1000);
|
||||
|
||||
@DataProvider(name = "fromMockedNetworkFailureToExpectedDocumentClientException")
|
||||
public Object[][] fromMockedNetworkFailureToExpectedDocumentClientException() {
|
||||
|
||||
return new Object[][] {
|
||||
// TODO: DANOBLE: add network failure exception test cases
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -596,11 +593,10 @@ public class RntbdTransportClientTest {
|
|||
@Test(enabled = false, groups = "direct")
|
||||
public void verifyGoneResponseMapsToGoneException() throws Exception {
|
||||
|
||||
final RntbdTransportClient.Options options = new RntbdTransportClient.Options(requestTimeout);
|
||||
final RntbdTransportClient.Options options = new RntbdTransportClient.Options.Builder(requestTimeout).build();
|
||||
final SslContext sslContext = SslContextBuilder.forClient().build();
|
||||
final UserAgentContainer userAgent = new UserAgentContainer();
|
||||
|
||||
try (final RntbdTransportClient transportClient = new RntbdTransportClient(options, sslContext, userAgent)) {
|
||||
try (final RntbdTransportClient transportClient = new RntbdTransportClient(options, sslContext)) {
|
||||
|
||||
final BaseAuthorizationTokenProvider authorizationTokenProvider = new BaseAuthorizationTokenProvider(
|
||||
RntbdTestConfiguration.AccountKey
|
||||
|
@ -623,13 +619,13 @@ public class RntbdTransportClientTest {
|
|||
|
||||
builder.put(HttpHeaders.AUTHORIZATION, token);
|
||||
|
||||
RxDocumentServiceRequest request = RxDocumentServiceRequest.create(OperationType.Read,
|
||||
final RxDocumentServiceRequest request = RxDocumentServiceRequest.create(OperationType.Read,
|
||||
ResourceType.DatabaseAccount,
|
||||
Paths.DATABASE_ACCOUNT_PATH_SEGMENT,
|
||||
builder.build()
|
||||
);
|
||||
|
||||
Single<StoreResponse> responseSingle = transportClient.invokeStoreAsync(physicalAddress, null, request);
|
||||
final Single<StoreResponse> responseSingle = transportClient.invokeStoreAsync(physicalAddress, null, request);
|
||||
|
||||
responseSingle.toObservable().toBlocking().subscribe(new Subscriber<StoreResponse>() {
|
||||
@Override
|
||||
|
@ -637,24 +633,24 @@ public class RntbdTransportClientTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
String cs = "Expected %s, not %s";
|
||||
assertTrue(error instanceof GoneException, String.format(cs, GoneException.class, error.getClass()));
|
||||
Throwable cause = error.getCause();
|
||||
public void onError(final Throwable error) {
|
||||
final String format = "Expected %s, not %s";
|
||||
assertTrue(error instanceof GoneException, String.format(format, GoneException.class, error.getClass()));
|
||||
final Throwable cause = error.getCause();
|
||||
if (cause != null) {
|
||||
// assumption: cosmos isn't listening on 10251
|
||||
assertTrue(cause instanceof ConnectException, String.format(cs, ConnectException.class, error.getClass()));
|
||||
assertTrue(cause instanceof ConnectException, String.format(format, ConnectException.class, error.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(StoreResponse response) {
|
||||
public void onNext(final StoreResponse response) {
|
||||
fail(String.format("Expected GoneException, not a StoreResponse: %s", response));
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception error) {
|
||||
String message = String.format("%s: %s", error.getClass(), error.getMessage());
|
||||
} catch (final Exception error) {
|
||||
final String message = String.format("%s: %s", error.getClass(), error.getMessage());
|
||||
fail(message, error);
|
||||
}
|
||||
}
|
||||
|
@ -671,10 +667,13 @@ public class RntbdTransportClientTest {
|
|||
*/
|
||||
@Test(enabled = false, groups = "unit", dataProvider = "fromMockedNetworkFailureToExpectedDocumentClientException")
|
||||
public void verifyNetworkFailure(
|
||||
FailureValidator.Builder builder,
|
||||
RxDocumentServiceRequest request,
|
||||
DocumentClientException exception
|
||||
final FailureValidator.Builder builder,
|
||||
final RxDocumentServiceRequest request,
|
||||
final DocumentClientException exception
|
||||
) {
|
||||
// TODO: DANOBLE: Implement RntbdTransportClientTest.verifyNetworkFailure
|
||||
// Links:
|
||||
// https://msdata.visualstudio.com/CosmosDB/_workitems/edit/378750
|
||||
throw new UnsupportedOperationException("TODO: DANOBLE: Implement this test");
|
||||
}
|
||||
|
||||
|
@ -687,68 +686,59 @@ public class RntbdTransportClientTest {
|
|||
*/
|
||||
@Test(enabled = true, groups = "unit", dataProvider = "fromMockedRntbdResponseToExpectedDocumentClientException")
|
||||
public void verifyRequestFailures(
|
||||
FailureValidator.Builder builder,
|
||||
RxDocumentServiceRequest request,
|
||||
RntbdResponse response
|
||||
final FailureValidator.Builder builder,
|
||||
final RxDocumentServiceRequest request,
|
||||
final RntbdResponse response
|
||||
) {
|
||||
final UserAgentContainer userAgent = new UserAgentContainer();
|
||||
final Duration timeout = Duration.ofMillis(100);
|
||||
|
||||
try (final RntbdTransportClient client = getRntbdTransportClientUnderTest(userAgent, timeout, response)) {
|
||||
|
||||
Single<StoreResponse> responseSingle;
|
||||
final Single<StoreResponse> responseSingle;
|
||||
|
||||
try {
|
||||
responseSingle = client.invokeStoreAsync(
|
||||
physicalAddress, new ResourceOperation(request.getOperationType(), request.getResourceType()), request
|
||||
);
|
||||
} catch (Exception error) {
|
||||
} catch (final Exception error) {
|
||||
throw new AssertionError(String.format("%s: %s", error.getClass(), error.getMessage()));
|
||||
}
|
||||
|
||||
validateFailure(responseSingle, builder.build());
|
||||
this.validateFailure(responseSingle, builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private static RntbdTransportClient getRntbdTransportClientUnderTest(
|
||||
UserAgentContainer userAgent,
|
||||
Duration requestTimeout,
|
||||
RntbdResponse expected
|
||||
final UserAgentContainer userAgent,
|
||||
final Duration requestTimeout,
|
||||
final RntbdResponse expected
|
||||
) {
|
||||
|
||||
final RntbdTransportClient.Options options = new RntbdTransportClient.Options(requestTimeout);
|
||||
final RntbdTransportClient.Options options = new RntbdTransportClient.Options.Builder(requestTimeout)
|
||||
.userAgent(userAgent)
|
||||
.build();
|
||||
|
||||
final SslContext sslContext;
|
||||
|
||||
try {
|
||||
sslContext = SslContextBuilder.forClient().build();
|
||||
} catch (Exception error) {
|
||||
} catch (final Exception error) {
|
||||
throw new AssertionError(String.format("%s: %s", error.getClass(), error.getMessage()));
|
||||
}
|
||||
|
||||
final RntbdTransportClient.EndpointFactory endpointFactory = spy(new RntbdTransportClient.EndpointFactory(
|
||||
options, sslContext, userAgent
|
||||
));
|
||||
|
||||
final RntbdTransportClient client = new RntbdTransportClient(endpointFactory);
|
||||
|
||||
doAnswer((Answer) invocation -> {
|
||||
|
||||
RntbdTransportClient.EndpointFactory factory = (RntbdTransportClient.EndpointFactory) invocation.getMock();
|
||||
URI physicalAddress = invocation.getArgumentAt(0, URI.class);
|
||||
return new FakeEndpoint(factory, physicalAddress, expected);
|
||||
|
||||
}).when(endpointFactory).createEndpoint(any());
|
||||
|
||||
return client;
|
||||
return new RntbdTransportClient(new FakeEndpoint.Provider(options, sslContext, expected));
|
||||
}
|
||||
|
||||
private void validateFailure(Single<StoreResponse> single, FailureValidator validator) {
|
||||
private void validateFailure(final Single<? extends StoreResponse> single, final FailureValidator validator) {
|
||||
validateFailure(single, validator, requestTimeout.toMillis());
|
||||
}
|
||||
|
||||
private static void validateFailure(Single<StoreResponse> single, FailureValidator validator, long timeout) {
|
||||
private static void validateFailure(
|
||||
final Single<? extends StoreResponse> single, final FailureValidator validator, final long timeout
|
||||
) {
|
||||
|
||||
TestSubscriber<StoreResponse> testSubscriber = new TestSubscriber<>();
|
||||
final TestSubscriber<StoreResponse> testSubscriber = new TestSubscriber<>();
|
||||
single.toObservable().subscribe(testSubscriber);
|
||||
testSubscriber.awaitTerminalEvent(timeout, TimeUnit.MILLISECONDS);
|
||||
testSubscriber.assertNotCompleted();
|
||||
|
@ -759,24 +749,24 @@ public class RntbdTransportClientTest {
|
|||
|
||||
// region Types
|
||||
|
||||
final private static class FakeChannel extends EmbeddedChannel {
|
||||
private static final class FakeChannel extends EmbeddedChannel {
|
||||
|
||||
final private static ServerProperties serverProperties = new ServerProperties("agent", "3.0.0");
|
||||
final private BlockingQueue<RntbdResponse> responses;
|
||||
private static final ServerProperties serverProperties = new ServerProperties("agent", "3.0.0");
|
||||
private final BlockingQueue<RntbdResponse> responses;
|
||||
|
||||
FakeChannel(BlockingQueue<RntbdResponse> responses, ChannelHandler... handlers) {
|
||||
FakeChannel(final BlockingQueue<RntbdResponse> responses, final ChannelHandler... handlers) {
|
||||
super(handlers);
|
||||
this.responses = responses;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleInboundMessage(Object message) {
|
||||
protected void handleInboundMessage(final Object message) {
|
||||
super.handleInboundMessage(message);
|
||||
assertTrue(message instanceof ByteBuf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleOutboundMessage(Object message) {
|
||||
protected void handleOutboundMessage(final Object message) {
|
||||
|
||||
assertTrue(message instanceof ByteBuf);
|
||||
|
||||
|
@ -787,18 +777,18 @@ public class RntbdTransportClientTest {
|
|||
|
||||
if (in.getUnsignedIntLE(4) == 0) {
|
||||
|
||||
RntbdContextRequest request = RntbdContextRequest.decode(in.copy());
|
||||
RntbdContext rntbdContext = RntbdContext.from(request, serverProperties, HttpResponseStatus.OK);
|
||||
final RntbdContextRequest request = RntbdContextRequest.decode(in.copy());
|
||||
final RntbdContext rntbdContext = RntbdContext.from(request, serverProperties, HttpResponseStatus.OK);
|
||||
|
||||
rntbdContext.encode(out);
|
||||
|
||||
} else {
|
||||
|
||||
RntbdResponse rntbdResponse;
|
||||
final RntbdResponse rntbdResponse;
|
||||
|
||||
try {
|
||||
rntbdResponse = this.responses.take();
|
||||
} catch (Exception error) {
|
||||
} catch (final Exception error) {
|
||||
throw new AssertionError(String.format("%s: %s", error.getClass(), error.getMessage()));
|
||||
}
|
||||
|
||||
|
@ -810,50 +800,90 @@ public class RntbdTransportClientTest {
|
|||
}
|
||||
}
|
||||
|
||||
final private static class FakeEndpoint implements RntbdTransportClient.Endpoint {
|
||||
private static final class FakeEndpoint implements RntbdEndpoint {
|
||||
|
||||
final RntbdRequestTimer requestTimer;
|
||||
final FakeChannel fakeChannel;
|
||||
final URI physicalAddress;
|
||||
final RntbdRequestManager requestManager;
|
||||
|
||||
FakeEndpoint(
|
||||
RntbdTransportClient.EndpointFactory factory,
|
||||
URI physicalAddress,
|
||||
RntbdResponse... expected
|
||||
private FakeEndpoint(
|
||||
final Config config, final RntbdRequestTimer timer, final URI physicalAddress,
|
||||
final RntbdResponse... expected
|
||||
) {
|
||||
|
||||
ArrayBlockingQueue<RntbdResponse> responses = new ArrayBlockingQueue<RntbdResponse>(
|
||||
final ArrayBlockingQueue<RntbdResponse> responses = new ArrayBlockingQueue<>(
|
||||
expected.length, true, Arrays.asList(expected)
|
||||
);
|
||||
|
||||
this.requestManager = new RntbdRequestManager();
|
||||
RntbdRequestManager requestManager = new RntbdRequestManager();
|
||||
this.physicalAddress = physicalAddress;
|
||||
this.requestTimer = timer;
|
||||
|
||||
this.fakeChannel = new FakeChannel(responses,
|
||||
new RntbdContextNegotiator(this.requestManager, factory.getUserAgent()),
|
||||
new RntbdContextNegotiator(requestManager, config.getUserAgent()),
|
||||
new RntbdRequestEncoder(),
|
||||
new RntbdResponseDecoder(),
|
||||
this.requestManager
|
||||
requestManager
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> close() {
|
||||
DefaultEventExecutor executor = new DefaultEventExecutor();
|
||||
Future<?> future = executor.newSucceededFuture(true);
|
||||
this.fakeChannel.close().syncUninterruptibly();
|
||||
return future;
|
||||
public String getName() {
|
||||
return "FakeEndpoint";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<StoreResponse> write(RntbdRequestArgs requestArgs) {
|
||||
final CompletableFuture<StoreResponse> responseFuture = this.requestManager.createStoreResponseFuture(requestArgs);
|
||||
this.fakeChannel.writeOutbound(requestArgs);
|
||||
return responseFuture;
|
||||
public void close() {
|
||||
this.fakeChannel.close().syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RntbdRequestRecord request(final RntbdRequestArgs requestArgs) {
|
||||
final RntbdRequestRecord requestRecord = new RntbdRequestRecord(requestArgs, this.requestTimer);
|
||||
this.fakeChannel.writeOutbound(requestRecord);
|
||||
return requestRecord;
|
||||
}
|
||||
|
||||
static class Provider implements RntbdEndpoint.Provider {
|
||||
|
||||
final Config config;
|
||||
final RntbdResponse expected;
|
||||
final RntbdRequestTimer timer;
|
||||
|
||||
Provider(RntbdTransportClient.Options options, SslContext sslContext, RntbdResponse expected) {
|
||||
this.config = new Config(options, sslContext, LogLevel.WARN);
|
||||
this.timer = new RntbdRequestTimer(config.getRequestTimeout());
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws RuntimeException {
|
||||
this.timer.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config config() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RntbdEndpoint get(URI physicalAddress) {
|
||||
return new FakeEndpoint(config, timer, physicalAddress, expected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RntbdEndpoint> list() {
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final private static class RntbdTestConfiguration {
|
||||
private static final class RntbdTestConfiguration {
|
||||
|
||||
static String AccountHost = System.getProperty("ACCOUNT_HOST",
|
||||
StringUtils.defaultString(
|
||||
|
|
234
pom.xml
234
pom.xml
|
@ -22,7 +22,8 @@
|
|||
SOFTWARE.
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.azure</groupId>
|
||||
<artifactId>azure-cosmosdb-parent</artifactId>
|
||||
|
@ -47,10 +48,13 @@
|
|||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||
<commons-validator.version>1.6</commons-validator.version>
|
||||
<commons-text.version>1.6</commons-text.version>
|
||||
<guava.version>27.0.1-jre</guava.version>
|
||||
<jackson-databind.version>2.9.8</jackson-databind.version>
|
||||
<java-uuid-generator.version>3.1.4</java-uuid-generator.version>
|
||||
<slf4j.version>1.7.6</slf4j.version>
|
||||
<log4j.version>1.2.17</log4j.version>
|
||||
<metrics.version>4.0.5</metrics.version>
|
||||
<mockito.version>1.10.19</mockito.version>
|
||||
<netty.version>4.1.32.Final</netty.version>
|
||||
<rxnetty.version>0.4.20</rxnetty.version>
|
||||
<rxjava.version>1.3.8</rxjava.version>
|
||||
|
@ -66,87 +70,87 @@
|
|||
<javadoc.opts/>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
<!-- unit test -->
|
||||
<id>unit</id>
|
||||
<properties>
|
||||
<env>default</env>
|
||||
<test.groups>unit</test.groups>
|
||||
</properties>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint -->
|
||||
<id>fast</id>
|
||||
<properties>
|
||||
<test.groups>simple</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint -->
|
||||
<id>long</id>
|
||||
<properties>
|
||||
<test.groups>long</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint -->
|
||||
<id>direct</id>
|
||||
<properties>
|
||||
<test.groups>direct</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint with multi master support -->
|
||||
<id>multi-master</id>
|
||||
<properties>
|
||||
<test.groups>multi-master</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- unit test -->
|
||||
<id>unit</id>
|
||||
<properties>
|
||||
<env>default</env>
|
||||
<test.groups>unit</test.groups>
|
||||
</properties>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint -->
|
||||
<id>fast</id>
|
||||
<properties>
|
||||
<test.groups>simple</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint -->
|
||||
<id>long</id>
|
||||
<properties>
|
||||
<test.groups>long</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint -->
|
||||
<id>direct</id>
|
||||
<properties>
|
||||
<test.groups>direct</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint with multi master support -->
|
||||
<id>multi-master</id>
|
||||
<properties>
|
||||
<test.groups>multi-master</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB endpoint -->
|
||||
<id>examples</id>
|
||||
|
@ -174,20 +178,20 @@
|
|||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB Emulator Endpoint -->
|
||||
<id>emulator</id>
|
||||
<properties>
|
||||
<test.groups>emulator</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<!-- integration tests, requires Cosmos DB Emulator Endpoint -->
|
||||
<id>emulator</id>
|
||||
<properties>
|
||||
<test.groups>emulator</test.groups>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- integration tests, requires Cosmos DB Emulator Endpoint -->
|
||||
<id>non-emulator</id>
|
||||
|
@ -288,25 +292,25 @@
|
|||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-report-plugin</artifactId>
|
||||
<version>2.22.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>3.0.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jxr-plugin</artifactId>
|
||||
<version>2.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-report-plugin</artifactId>
|
||||
<version>2.22.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>3.0.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jxr-plugin</artifactId>
|
||||
<version>2.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
@ -235,10 +235,9 @@ public class RxDocumentClientImpl implements AsyncDocumentClient, IAuthorization
|
|||
public RxDocumentClientImpl(URI serviceEndpoint, String masterKeyOrResourceToken, ConnectionPolicy connectionPolicy,
|
||||
ConsistencyLevel consistencyLevel, Configs configs) {
|
||||
|
||||
logger.info(
|
||||
"Initializing DocumentClient with"
|
||||
+ " serviceEndpoint [{}], ConnectionPolicy [{}], ConsistencyLevel [{}]",
|
||||
serviceEndpoint, connectionPolicy, consistencyLevel);
|
||||
logger.info("Initializing DocumentClient with serviceEndpoint [{}], connectionPolicy [{}], "
|
||||
+ "consistencyLevel [{}], protocol [{}]", serviceEndpoint, connectionPolicy,
|
||||
consistencyLevel, configs.getProtocol());
|
||||
|
||||
this.configs = configs;
|
||||
this.masterKeyOrResourceToken = masterKeyOrResourceToken;
|
||||
|
|
Загрузка…
Ссылка в новой задаче