public async Task<IActionResult> GetQueueLengthAsync() { ServiceUriBuilder uriBuilder = new ServiceUriBuilder(TenantDataServiceName); Uri serviceUri = uriBuilder.Build(); // service may be partitioned. // this will aggregate the queue lengths from each partition ServicePartitionList partitions = await this.fabricClient.QueryManager.GetPartitionListAsync(serviceUri); long count = 0; foreach (Partition partition in partitions) { Uri getUrl = new HttpServiceUriBuilder() .SetServiceName(serviceUri) .SetPartitionKey(((Int64RangePartitionInformation) partition.PartitionInformation).LowKey) .SetServicePathAndQuery($"/api/devices/queue/length") .Build(); HttpResponseMessage response = await this.httpClient.GetAsync(getUrl, this.serviceCancellationToken); if (response.StatusCode != System.Net.HttpStatusCode.OK) { return this.StatusCode((int) response.StatusCode); } string result = await response.Content.ReadAsStringAsync(); count += Int64.Parse(result); } return this.Ok(count); }
/// <summary> /// http://fabric/app/service/#/partitionkey/any|primary|secondary/endpoint-name/api-path /// </summary> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> protected override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { ServicePartitionResolver resolver = ServicePartitionResolver.GetDefault(); ResolvedServicePartition partition = null; HttpServiceUriBuilder uriBuilder = new HttpServiceUriBuilder(request.RequestUri); int retries = MaxRetries; int retryDelay = InitialRetryDelayMs; bool resolveAddress = true; HttpResponseMessage lastResponse = null; Exception lastException = null; while (retries-- > 0) { lastResponse = null; cancellationToken.ThrowIfCancellationRequested(); if (resolveAddress) { partition = partition != null ? await resolver.ResolveAsync(partition, cancellationToken) : await resolver.ResolveAsync(uriBuilder.ServiceName, uriBuilder.PartitionKey, cancellationToken); string serviceEndpointJson; switch (uriBuilder.Target) { case HttpServiceUriTarget.Default: case HttpServiceUriTarget.Primary: serviceEndpointJson = partition.GetEndpoint().Address; break; case HttpServiceUriTarget.Secondary: serviceEndpointJson = partition.Endpoints.ElementAt(this.random.Next(1, partition.Endpoints.Count)).Address; break; case HttpServiceUriTarget.Any: default: serviceEndpointJson = partition.Endpoints.ElementAt(this.random.Next(0, partition.Endpoints.Count)).Address; break; } string endpointUrl = JObject.Parse(serviceEndpointJson)["Endpoints"][uriBuilder.EndpointName].Value <string>(); request.RequestUri = new Uri($"{endpointUrl.TrimEnd('/')}/{uriBuilder.ServicePathAndQuery.TrimStart('/')}", UriKind.Absolute); } try { lastResponse = await base.SendAsync(request, cancellationToken); if (lastResponse.StatusCode == HttpStatusCode.NotFound || lastResponse.StatusCode == HttpStatusCode.ServiceUnavailable) { resolveAddress = true; } else { return(lastResponse); } } catch (TimeoutException te) { lastException = te; resolveAddress = true; } catch (SocketException se) { lastException = se; resolveAddress = true; } catch (HttpRequestException hre) { lastException = hre; resolveAddress = true; } catch (Exception ex) { lastException = ex; WebException we = ex as WebException; if (we == null) { we = ex.InnerException as WebException; } if (we != null) { HttpWebResponse errorResponse = we.Response as HttpWebResponse; // the following assumes port sharing // where a port is shared by multiple replicas within a host process using a single web host (e.g., http.sys). if (we.Status == WebExceptionStatus.ProtocolError) { if (errorResponse.StatusCode == HttpStatusCode.NotFound || errorResponse.StatusCode == HttpStatusCode.ServiceUnavailable) { // This could either mean we requested an endpoint that does not exist in the service API (a user error) // or the address that was resolved by fabric client is stale (transient runtime error) in which we should re-resolve. resolveAddress = true; } // On any other HTTP status codes, re-throw the exception to the caller. throw; } if (we.Status == WebExceptionStatus.Timeout || we.Status == WebExceptionStatus.RequestCanceled || we.Status == WebExceptionStatus.ConnectionClosed || we.Status == WebExceptionStatus.ConnectFailure) { resolveAddress = true; } } else { throw; } } await Task.Delay(retryDelay); retryDelay += retryDelay; } if (lastResponse != null) { return(lastResponse); } else { throw lastException; } }
/// <summary> /// http://fabric/app/service/#/partitionkey/any|primary|secondary/endpoint-name/api-path /// </summary> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { ServicePartitionResolver resolver = ServicePartitionResolver.GetDefault(); ResolvedServicePartition partition = null; HttpServiceUriBuilder uriBuilder = new HttpServiceUriBuilder(request.RequestUri); int retries = MaxRetries; int retryDelay = InitialRetryDelayMs; bool resolveAddress = true; HttpResponseMessage lastResponse = null; Exception lastException = null; while (retries --> 0) { lastResponse = null; cancellationToken.ThrowIfCancellationRequested(); if (resolveAddress) { partition = partition != null ? await resolver.ResolveAsync(partition, cancellationToken) : await resolver.ResolveAsync(uriBuilder.ServiceName, uriBuilder.PartitionKey, cancellationToken); string serviceEndpointJson; switch (uriBuilder.Target) { case HttpServiceUriTarget.Default: case HttpServiceUriTarget.Primary: serviceEndpointJson = partition.GetEndpoint().Address; break; case HttpServiceUriTarget.Secondary: serviceEndpointJson = partition.Endpoints.ElementAt(this.random.Next(1, partition.Endpoints.Count)).Address; break; case HttpServiceUriTarget.Any: default: serviceEndpointJson = partition.Endpoints.ElementAt(this.random.Next(0, partition.Endpoints.Count)).Address; break; } string endpointUrl = JObject.Parse(serviceEndpointJson)["Endpoints"][uriBuilder.EndpointName].Value<string>(); request.RequestUri = new Uri($"{endpointUrl.TrimEnd('/')}/{uriBuilder.ServicePathAndQuery.TrimStart('/')}", UriKind.Absolute); } try { lastResponse = await base.SendAsync(request, cancellationToken); if (lastResponse.StatusCode == HttpStatusCode.NotFound || lastResponse.StatusCode == HttpStatusCode.ServiceUnavailable) { resolveAddress = true; } else { return lastResponse; } } catch (TimeoutException te) { lastException = te; resolveAddress = true; } catch (SocketException se) { lastException = se; resolveAddress = true; } catch (HttpRequestException hre) { lastException = hre; resolveAddress = true; } catch (Exception ex) { lastException = ex; WebException we = ex as WebException; if (we == null) { we = ex.InnerException as WebException; } if (we != null) { HttpWebResponse errorResponse = we.Response as HttpWebResponse; // the following assumes port sharing // where a port is shared by multiple replicas within a host process using a single web host (e.g., http.sys). if (we.Status == WebExceptionStatus.ProtocolError) { if (errorResponse.StatusCode == HttpStatusCode.NotFound || errorResponse.StatusCode == HttpStatusCode.ServiceUnavailable) { // This could either mean we requested an endpoint that does not exist in the service API (a user error) // or the address that was resolved by fabric client is stale (transient runtime error) in which we should re-resolve. resolveAddress = true; } // On any other HTTP status codes, re-throw the exception to the caller. throw; } if (we.Status == WebExceptionStatus.Timeout || we.Status == WebExceptionStatus.RequestCanceled || we.Status == WebExceptionStatus.ConnectionClosed || we.Status == WebExceptionStatus.ConnectFailure) { resolveAddress = true; } } else { throw; } } await Task.Delay(retryDelay); retryDelay += retryDelay; } if (lastResponse != null) { return lastResponse; } else { throw lastException; } }
/// <summary> /// This is the main entry point for your service replica. /// This method executes when this replica of your service becomes primary and has write status. /// </summary> /// <param name="cancellationToken">Canceled when Service Fabric needs to shut down this service replica.</param> protected override async Task RunAsync(CancellationToken cancellationToken) { // Get the IoT Hub connection string from the Settings.xml config file // from a configuration package named "Config" string iotHubConnectionString = this.Context.CodePackageActivationContext .GetConfigurationPackageObject("Config") .Settings .Sections["IoTHubConfigInformation"] .Parameters["ConnectionString"] .Value; // These Reliable Dictionaries are used to keep track of our position in IoT Hub. // If this service fails over, this will allow it to pick up where it left off in the event stream. IReliableDictionary<string, string> offsetDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<string, string>>(OffsetDictionaryName); IReliableDictionary<string, long> epochDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<string, long>>(EpochDictionaryName); // Each partition of this service corresponds to a partition in IoT Hub. // IoT Hub partitions are numbered 0..n-1, up to n = 32. // This service needs to use an identical partitioning scheme. // The low key of every partition corresponds to an IoT Hub partition. Int64RangePartitionInformation partitionInfo = (Int64RangePartitionInformation)this.Partition.PartitionInfo; long servicePartitionKey = partitionInfo.LowKey; EventHubReceiver eventHubReceiver = null; MessagingFactory messagingFactory = null; try { // Get an EventHubReceiver and the MessagingFactory used to create it. // The EventHubReceiver is used to get events from IoT Hub. // The MessagingFactory is just saved for later so it can be closed before RunAsync exits. Tuple<EventHubReceiver, MessagingFactory> iotHubInfo = await this.ConnectToIoTHubAsync(iotHubConnectionString, servicePartitionKey, epochDictionary, offsetDictionary); eventHubReceiver = iotHubInfo.Item1; messagingFactory = iotHubInfo.Item2; // HttpClient is designed as a shared object. // A single instance should be used throughout the lifetime of RunAsync. using (HttpClient httpClient = new HttpClient(new HttpServiceClientHandler())) { int offsetIteration = 0; while (true) { cancellationToken.ThrowIfCancellationRequested(); try { // It's important to set a low wait time here in lieu of a cancellation token // so that this doesn't block RunAsync from exiting when Service Fabric needs it to complete. // ReceiveAsync is a long-poll operation, so the timeout should not be too low, // yet not too high to block RunAsync from exiting within a few seconds. using (EventData eventData = await eventHubReceiver.ReceiveAsync(TimeSpan.FromSeconds(5))) { if (eventData == null) { continue; } string tenantId = (string)eventData.Properties["TenantID"]; string deviceId = (string)eventData.Properties["DeviceID"]; // This is the named service instance of the tenant data service that the event should be sent to. // The tenant ID is part of the named service instance name. // The incoming device data stream specifie which tenant the data belongs to. Uri tenantServiceName = new Uri($"{Names.TenantApplicationNamePrefix}/{tenantId}/{Names.TenantDataServiceName}"); long tenantServicePartitionKey = FnvHash.Hash(deviceId); // The tenant data service exposes an HTTP API. // For incoming device events, the URL is /api/events/{deviceId} // This sets up a URL and sends a POST request with the device JSON payload. Uri postUrl = new HttpServiceUriBuilder() .SetServiceName(tenantServiceName) .SetPartitionKey(tenantServicePartitionKey) .SetServicePathAndQuery($"/api/events/{deviceId}") .Build(); // The device stream payload isn't deserialized and buffered in memory here. // Instead, we just can just hook the incoming stream from Iot Hub right into the HTTP request stream. using (Stream eventStream = eventData.GetBodyStream()) { using (StreamContent postContent = new StreamContent(eventStream)) { postContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); HttpResponseMessage response = await httpClient.PostAsync(postUrl, postContent, cancellationToken); ServiceEventSource.Current.ServiceMessage( this.Context, "Sent event data to tenant service '{0}' with partition key '{1}'. Result: {2}", tenantServiceName, tenantServicePartitionKey, response.StatusCode.ToString()); if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) { // This service expects the receiving tenant service to return HTTP 400 if the device message was malformed. // In this example, the message is simply logged. // Your application should handle all possible error status codes from the receiving service // and treat the message as a "poison" message. // Message processing should be allowed to continue after a poison message is detected. string responseContent = await response.Content.ReadAsStringAsync(); ServiceEventSource.Current.ServiceMessage( this.Context, "Tenant service '{0}' returned HTTP 400 due to a bad device message from device '{1}'. Error message: '{2}'", tenantServiceName, deviceId, responseContent); } } } // Save the current Iot Hub data stream offset. // This will allow the service to pick up from its current location if it fails over. // Duplicate device messages may still be sent to the the tenant service // if this service fails over after the message is sent but before the offset is saved. if (++offsetIteration % OffsetInterval == 0) { ServiceEventSource.Current.ServiceMessage( this.Context, "Saving offset {0}", eventData.Offset); using (ITransaction tx = this.StateManager.CreateTransaction()) { await offsetDictionary.SetAsync(tx, "offset", eventData.Offset); await tx.CommitAsync(); } offsetIteration = 0; } } } catch (TimeoutException te) { // transient error. Retry. ServiceEventSource.Current.ServiceMessage(this.Context, $"TimeoutException in RunAsync: {te.ToString()}"); } catch (FabricTransientException fte) { // transient error. Retry. ServiceEventSource.Current.ServiceMessage(this.Context, $"FabricTransientException in RunAsync: {fte.ToString()}"); } catch (FabricNotPrimaryException) { // not primary any more, time to quit. return; } catch (Exception ex) { ServiceEventSource.Current.ServiceMessage(this.Context, ex.ToString()); throw; } } } } finally { if (messagingFactory != null) { await messagingFactory.CloseAsync(); } } }
public async Task<IActionResult> GetDevicesAsync() { ServiceUriBuilder uriBuilder = new ServiceUriBuilder(TenantDataServiceName); Uri serviceUri = uriBuilder.Build(); // service may be partitioned. // this will aggregate device IDs from all partitions ServicePartitionList partitions = await this.fabricClient.QueryManager.GetPartitionListAsync(serviceUri); List<DeviceViewModel> deviceViewModels = new List<DeviceViewModel>(); foreach (Partition partition in partitions) { Uri getUrl = new HttpServiceUriBuilder() .SetServiceName(serviceUri) .SetPartitionKey(((Int64RangePartitionInformation) partition.PartitionInformation).LowKey) .SetServicePathAndQuery($"/api/devices") .Build(); HttpResponseMessage response = await this.httpClient.GetAsync(getUrl, this.serviceCancellationToken); if (response.StatusCode != System.Net.HttpStatusCode.OK) { return this.StatusCode((int) response.StatusCode); } JsonSerializer serializer = new JsonSerializer(); using (StreamReader streamReader = new StreamReader(await response.Content.ReadAsStreamAsync())) { using (JsonTextReader jsonReader = new JsonTextReader(streamReader)) { List<DeviceViewModel> result = serializer.Deserialize<List<DeviceViewModel>>(jsonReader); if (result != null) { deviceViewModels.AddRange(result); } } } } return this.Ok(deviceViewModels); }