/// <summary> /// Try to activate endpoint on any supervisor in site /// </summary> /// <param name="endpoint"></param> /// <param name="additional"></param> /// <param name="context"></param> /// <param name="ct"></param> /// <returns></returns> private async Task <bool> ActivateAsync(EndpointRegistration endpoint, IEnumerable <string> additional, RegistryOperationContextModel context, CancellationToken ct) { // Get site of this endpoint var site = endpoint.SiteId; if (string.IsNullOrEmpty(site)) { // Use discovery id gateway part if no site found site = DiscovererModelEx.ParseDeviceId(endpoint.DiscovererId, out _); if (string.IsNullOrEmpty(site)) { // Try supervisor id gateway part site = DiscovererModelEx.ParseDeviceId(endpoint.SupervisorId, out _); } } // Get all supervisors in site endpoint.SiteId = site; var supervisorsInSite = await _supervisors.QueryAllSupervisorsAsync( new SupervisorQueryModel { SiteId = site }); var candidateSupervisors = supervisorsInSite.Select(s => s.Id) .ToList().Shuffle(); // Add all supervisors that managed this endpoint before. // TODO: Consider removing as it is a source of bugs if (additional != null) { candidateSupervisors.AddRange(additional); } // Remove previously failing one candidateSupervisors.Remove(endpoint.SupervisorId); // Loop through all randomly and try to take one that works. foreach (var supervisorId in candidateSupervisors) { endpoint.SupervisorId = supervisorId; endpoint.Activated = false; try { await ActivateAsync(endpoint, context, ct); _logger.Information("Activate twin on supervisor {supervisorId}!", supervisorId); // Done - endpoint was also patched thus has new supervisor id return(true); } catch (Exception ex) { _logger.Debug(ex, "Failed to activate twin on supervisor {supervisorId} " + "- trying next...", supervisorId); } } // Failed return(false); }
/// <inheritdoc/> public async Task ProcessDiscoveryResultsAsync(string discovererId, DiscoveryResultModel result, IEnumerable <DiscoveryEventModel> events) { if (string.IsNullOrEmpty(discovererId)) { throw new ArgumentNullException(nameof(discovererId)); } var gatewayId = DiscovererModelEx.ParseDeviceId(discovererId, out _); if (result == null) { throw new ArgumentNullException(nameof(result)); } if (events == null) { throw new ArgumentNullException(nameof(events)); } if ((result.RegisterOnly ?? false) && !events.Any()) { return; } var sites = events.Select(e => e.Application.SiteId).Distinct(); if (sites.Count() > 1) { throw new ArgumentException("Unexpected number of sites in discovery"); } var siteId = sites.SingleOrDefault() ?? gatewayId; var gateway = await _gateways.GetGatewayAsync(gatewayId); // // Merge in global discovery configuration into the one sent // by the discoverer. // if (result.DiscoveryConfig == null) { // Use global discovery configuration result.DiscoveryConfig = gateway.Modules?.Discoverer?.DiscoveryConfig; } else { if (result.DiscoveryConfig.ActivationFilter == null) { // Use global activation filter result.DiscoveryConfig.ActivationFilter = gateway.Modules?.Discoverer?.DiscoveryConfig?.ActivationFilter; } } // Process discovery events await _applications.ProcessDiscoveryEventsAsync(siteId, discovererId, gateway.Modules?.Supervisor?.Id, result, events); }
/// <summary> /// Create server model /// </summary> /// <param name="result"></param> /// <param name="hostAddress"></param> /// <param name="siteId"></param> /// <param name="gatewayId"></param> /// <param name="moduleId"></param> /// <returns></returns> public static ApplicationRegistrationModel ToServiceModel(this DiscoveredEndpointModel result, string hostAddress, string siteId, string gatewayId, string moduleId) { var type = result.Description.Server.ApplicationType.ToServiceType() ?? ApplicationType.Server; var discovererId = DiscovererModelEx.CreateDiscovererId(gatewayId, moduleId); return(new ApplicationRegistrationModel { Application = new ApplicationInfoModel { SiteId = siteId, DiscovererId = discovererId, ApplicationType = type, ApplicationId = ApplicationInfoModelEx.CreateApplicationId(siteId ?? gatewayId, result.Description.Server.ApplicationUri, type), // TODO: Assign at onboarder and leave null ProductUri = result.Description.Server.ProductUri, ApplicationUri = result.Description.Server.ApplicationUri, DiscoveryUrls = new HashSet <string>(result.Description.Server.DiscoveryUrls), DiscoveryProfileUri = result.Description.Server.DiscoveryProfileUri, HostAddresses = new HashSet <string> { hostAddress }, ApplicationName = result.Description.Server.ApplicationName.Text, LocalizedNames = string.IsNullOrEmpty(result.Description.Server.ApplicationName.Locale) ? null : new Dictionary <string, string> { [result.Description.Server.ApplicationName.Locale] = result.Description.Server.ApplicationName.Text }, NotSeenSince = null, Certificate = result.Description.ServerCertificate, Capabilities = new HashSet <string>(result.Capabilities) }, Endpoints = new List <EndpointRegistrationModel> { new EndpointRegistrationModel { SiteId = siteId, DiscovererId = discovererId, SupervisorId = null, Id = null, SecurityLevel = result.Description.SecurityLevel, AuthenticationMethods = result.Description.UserIdentityTokens.ToServiceModel(), EndpointUrl = result.Description.EndpointUrl, // Reported Endpoint = new EndpointModel { Url = result.AccessibleEndpointUrl, // Accessible AlternativeUrls = new HashSet <string> { result.AccessibleEndpointUrl, result.Description.EndpointUrl, }, Certificate = result.Description.ServerCertificate, SecurityMode = result.Description.SecurityMode.ToServiceType() ?? SecurityMode.None, SecurityPolicy = result.Description.SecurityPolicyUri } } } }); }
/// <summary> /// Send application alert /// </summary> /// <param name="application"></param> /// <param name="message"></param> /// <param name="usedConfiguration"></param> /// <returns></returns> private Task SendApplicationAlertAsync(ApplicationInfoModel application, string message, string usedConfiguration) { #if !USE_APPLICATION_IDENTITY var deviceId = DiscovererModelEx.ParseDeviceId( application.DiscovererId, out var moduleId); #else var deviceId = application.ApplicationId; var moduleId = (string)null; #endif return(SendAlertAsync(deviceId, moduleId, message, usedConfiguration, FlattenToDict(application))); }
/// <summary> /// Send endpoint alert /// </summary> /// <param name="endpoint"></param> /// <param name="message"></param> /// <param name="usedConfiguration"></param> /// <returns></returns> private Task SendEndpointAlertAsync(EndpointInfoModel endpoint, string message, string usedConfiguration) { #if USE_DISCOVERER_IDENTITY var deviceId = DiscovererModelEx.ParseDeviceId( endpoint.Registration.DiscovererId, out var moduleId); #else var deviceId = endpoint.Registration.Id; var moduleId = (string)null; #endif return(SendAlertAsync(deviceId, moduleId, message, usedConfiguration, FlattenToDict(endpoint.Registration))); }
/// <summary> /// helper to invoke service /// </summary> /// <typeparam name="T"></typeparam> /// <param name="discovererId"></param> /// <param name="service"></param> /// <param name="request"></param> /// <param name="ct"></param> /// <returns></returns> private async Task CallServiceOnDiscovererAsync <T>(string discovererId, string service, T request, CancellationToken ct) { if (string.IsNullOrEmpty(discovererId)) { throw new ArgumentNullException(nameof(discovererId)); } var sw = Stopwatch.StartNew(); var deviceId = DiscovererModelEx.ParseDeviceId(discovererId, out var moduleId); var result = await _client.CallMethodAsync(deviceId, moduleId, service, _serializer.SerializeToString(request), null, ct); _logger.Debug("Calling discoverer service '{service}' on " + "{deviceId}/{moduleId} took {elapsed} ms.", service, deviceId, moduleId, sw.ElapsedMilliseconds); }
/// <inheritdoc/> public async Task <DiscovererModel> GetDiscovererAsync(string id, CancellationToken ct) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(nameof(id)); } var deviceId = DiscovererModelEx.ParseDeviceId(id, out var moduleId); var device = await _iothub.GetAsync(deviceId, moduleId, ct); var registration = device.ToEntityRegistration() as DiscovererRegistration; if (registration == null) { throw new ResourceNotFoundException( $"{id} is not a discoverer registration."); } return(registration.ToServiceModel()); }
public void ProcessOneDiscoveryWithDifferentDiscoverersFromExistingWhenExistingDisabled() { var fix = new Fixture(); var discoverer2 = DiscovererModelEx.CreateDiscovererId(fix.Create <string>(), fix.Create <string>()); // Readjust existing to be reported from different Discoverer... CreateFixtures(out var site, out var discoverer, out var supervisor, out var publisher, out var gateway, out var existing, out var found, out var registry, -1, x => { x.Application.DiscovererId = discoverer2; x.Endpoints.ForEach(e => e.DiscovererId = discoverer2); return(x); }, true); // Found one app and endpoint found = new List <DiscoveryEventModel> { found.First() }; var count = registry.Devices.Count(); // Assert disabled items are now enabled using (var mock = AutoMock.GetLoose()) { // Setup var service = Setup(mock, registry); // Run service.ProcessDiscoveryResultsAsync(discoverer, new DiscoveryResultModel(), found).Wait(); // Assert var inreg = ApplicationsIn(registry); Assert.Equal(count, registry.Devices.Count()); Assert.False(inreg.IsSameAs(existing)); Assert.Equal(discoverer, inreg.First().Application.DiscovererId); Assert.Null(inreg.First().Application.NotSeenSince); Assert.Equal(discoverer, inreg.First().Endpoints.First().DiscovererId); } }
public void ProcessDiscoveryWithDifferentDiscoverersSameSiteApplications() { var fix = new Fixture(); var discoverer2 = DiscovererModelEx.CreateDiscovererId(fix.Create <string>(), fix.Create <string>()); // Readjust existing to be reported from different Discoverer... CreateFixtures(out var site, out var discoverer, out var supervisor, out var publisher, out var gateway, out var existing, out var found, out var registry, -1, x => { x.Application.DiscovererId = discoverer2; x.Endpoints.ForEach(e => e.DiscovererId = discoverer2); return(x); }); // Assert no changes using (var mock = Setup(registry, out var service)) { // Run service.ProcessDiscoveryResultsAsync(discoverer, new DiscoveryResultModel(), found).Wait(); // Assert Assert.True(ApplicationsIn(registry).IsSameAs(existing)); } }
/// <inheritdoc/> public async Task HandleAsync(string deviceId, string moduleId, byte[] payload, IDictionary <string, string> properties, Func <Task> checkpoint) { DiscoveryEventModel discovery; try { discovery = _serializer.Deserialize <DiscoveryEventModel>(payload); } catch (Exception ex) { _logger.Error(ex, "Failed to convert discovery result {json}", Encoding.UTF8.GetString(payload)); return; } try { var discovererId = DiscovererModelEx.CreateDiscovererId( deviceId, moduleId?.ToString()); await ProcessServerEndpointDiscoveryAsync(discovererId, discovery, checkpoint); } catch (Exception ex) { _logger.Error(ex, "Handling discovery event failed with exception - skip"); } }
/// <summary> /// Helper to create fixtures /// </summary> /// <param name="site"></param> /// <param name="discoverer"></param> /// <param name="existing"></param> /// <param name="found"></param> /// <param name="registry"></param> /// <param name="countDevices"></param> /// <param name="fixup"></param> /// <param name="disable"></param> private void CreateFixtures(out string site, out string discoverer, out string supervisor, out string publisher, out string gateway, out List <ApplicationRegistrationModel> existing, out List <DiscoveryEventModel> found, out IoTHubServices registry, int countDevices = -1, Func <ApplicationRegistrationModel, ApplicationRegistrationModel> fixup = null, bool disable = false) { var fix = new Fixture(); // Create template applications and endpoints fix.Customizations.Add(new TypeRelay(typeof(VariantValue), typeof(VariantValue))); fix.Behaviors.OfType <ThrowingRecursionBehavior>().ToList() .ForEach(b => fix.Behaviors.Remove(b)); fix.Behaviors.Add(new OmitOnRecursionBehavior()); var sitex = site = fix.Create <string>(); gateway = fix.Create <string>(); var Gateway = (new GatewayModel { SiteId = site, Id = gateway }.ToGatewayRegistration().ToDeviceTwin(), new DeviceModel { Id = gateway }); var module = fix.Create <string>(); var discovererx = discoverer = DiscovererModelEx.CreateDiscovererId(gateway, module); var Discoverer = (new DiscovererModel { SiteId = site, Id = discovererx }.ToDiscovererRegistration().ToDeviceTwin(_serializer), new DeviceModel { Id = gateway, ModuleId = module }); module = fix.Create <string>(); var supervisorx = supervisor = SupervisorModelEx.CreateSupervisorId(gateway, module); var Supervisor = (new SupervisorModel { SiteId = site, Id = supervisorx }.ToSupervisorRegistration().ToDeviceTwin(_serializer), new DeviceModel { Id = gateway, ModuleId = module }); module = fix.Create <string>(); var publisherx = publisher = PublisherModelEx.CreatePublisherId(gateway, module); var Publisher = (new PublisherModel { SiteId = site, Id = publisherx }.ToPublisherRegistration().ToDeviceTwin(_serializer), new DeviceModel { Id = gateway, ModuleId = module }); var template = fix .Build <ApplicationRegistrationModel>() .Without(x => x.Application) .Do(c => c.Application = fix .Build <ApplicationInfoModel>() .Without(x => x.NotSeenSince) .With(x => x.SiteId, sitex) .With(x => x.DiscovererId, discovererx) .Create()) .Without(x => x.Endpoints) .Do(c => c.Endpoints = fix .Build <EndpointRegistrationModel>() .With(x => x.SiteId, sitex) .With(x => x.DiscovererId, discovererx) .With(x => x.SupervisorId, supervisorx) .CreateMany(5) .ToList()) .CreateMany(5) .ToList(); template.ForEach(a => a.Application.ApplicationId = ApplicationInfoModelEx.CreateApplicationId(a.Application) ); // Create discovery results from template var i = 0; var now = DateTime.UtcNow; found = template .SelectMany(a => a.Endpoints.Select( e => new DiscoveryEventModel { Application = a.Application, Registration = e, Index = i++, TimeStamp = now })) .ToList(); // Clone and fixup existing applications as per test case existing = template .Select(e => e.Clone()) .Select(fixup ?? (a => a)) .ToList(); // and fill registry with them... var appdevices = existing .Select(a => a.Application.ToApplicationRegistration(disable)) .Select(a => a.ToDeviceTwin(_serializer)) .Select(d => (d, new DeviceModel { Id = d.Id })); var epdevices = existing .SelectMany(a => a.Endpoints .Select(e => new EndpointInfoModel { ApplicationId = a.Application.ApplicationId, Registration = e }.ToEndpointRegistration(_serializer, disable)) .Select(e => e.ToDeviceTwin(_serializer))) .Select(d => (d, new DeviceModel { Id = d.Id })); appdevices = appdevices.Concat(epdevices); if (countDevices != -1) { appdevices = appdevices.Take(countDevices); } registry = IoTHubServices.Create(appdevices .Concat(Gateway.YieldReturn()) .Concat(Discoverer.YieldReturn()) .Concat(Supervisor.YieldReturn()) .Concat(Publisher.YieldReturn())); }
public void ProcessDiscoveryWithNoResultsAndNoExistingApplications() { var found = new List <DiscoveryEventModel>(); var fix = new Fixture(); var gateway = fix.Create <string>(); var Gateway = (new GatewayModel { Id = gateway }.ToGatewayRegistration().ToDeviceTwin(), new DeviceModel { Id = gateway }); var module = fix.Create <string>(); var discoverer = DiscovererModelEx.CreateDiscovererId(gateway, module); var Discoverer = (new DiscovererModel { Id = discoverer }.ToDiscovererRegistration().ToDeviceTwin(_serializer), new DeviceModel { Id = gateway, ModuleId = module }); module = fix.Create <string>(); var supervisor = SupervisorModelEx.CreateSupervisorId(gateway, module); var Supervisor = (new SupervisorModel { Id = supervisor }.ToSupervisorRegistration().ToDeviceTwin(_serializer), new DeviceModel { Id = gateway, ModuleId = module }); module = fix.Create <string>(); var publisher = PublisherModelEx.CreatePublisherId(gateway, module); var Publisher = (new PublisherModel { Id = publisher }.ToPublisherRegistration().ToDeviceTwin(_serializer), new DeviceModel { Id = gateway, ModuleId = module }); var registry = IoTHubServices.Create(Gateway.YieldReturn() // Single device .Append(Discoverer) .Append(Supervisor) .Append(Publisher)); using (var mock = AutoMock.GetLoose(builder => { // Setup builder.RegisterInstance(registry).As <IIoTHubTwinServices>(); builder.RegisterType <ApplicationTwins>().As <IApplicationRepository>(); builder.RegisterType <DiscovererRegistry>().As <IDiscovererRegistry>(); builder.RegisterType <SupervisorRegistry>().As <ISupervisorRegistry>(); builder.RegisterType <PublisherRegistry>().As <IPublisherRegistry>(); builder.RegisterType <GatewayRegistry>().As <IGatewayRegistry>(); builder.RegisterType <ApplicationRegistry>().As <IApplicationBulkProcessor>(); builder.RegisterType <EndpointRegistry>().As <IEndpointBulkProcessor>(); })) { var service = mock.Create <DiscoveryProcessor>(); // Run service.ProcessDiscoveryResultsAsync(discoverer, new DiscoveryResultModel(), found).Wait(); // Assert Assert.Single(registry.Devices); Assert.Equal(gateway, registry.Devices.First().Device.Id); } }
/// <inheritdoc/> public async Task UpdateDiscovererAsync(string discovererId, DiscovererUpdateModel request, CancellationToken ct) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (string.IsNullOrEmpty(discovererId)) { throw new ArgumentException(nameof(discovererId)); } // Get existing endpoint and compare to see if we need to patch. var deviceId = DiscovererModelEx.ParseDeviceId(discovererId, out var moduleId); while (true) { try { var twin = await _iothub.GetAsync(deviceId, moduleId, ct); if (twin.Id != deviceId && twin.ModuleId != moduleId) { throw new ArgumentException("Id must be same as twin to patch", nameof(discovererId)); } var registration = twin.ToEntityRegistration(true) as DiscovererRegistration; if (registration == null) { throw new ResourceNotFoundException( $"{discovererId} is not a discoverer registration."); } // Update registration from update request var patched = registration.ToServiceModel(); if (request.Discovery != null) { patched.RequestedMode = (DiscoveryMode)request.Discovery; } if (request.SiteId != null) { patched.SiteId = string.IsNullOrEmpty(request.SiteId) ? null : request.SiteId; } if (request.LogLevel != null) { patched.LogLevel = request.LogLevel == TraceLogLevel.Information ? null : request.LogLevel; } if (request.DiscoveryConfig != null) { if (patched.RequestedConfig == null) { patched.RequestedConfig = new DiscoveryConfigModel(); } if (request.DiscoveryConfig.AddressRangesToScan != null) { patched.RequestedConfig.AddressRangesToScan = string.IsNullOrEmpty( request.DiscoveryConfig.AddressRangesToScan.Trim()) ? null : request.DiscoveryConfig.AddressRangesToScan; } if (request.DiscoveryConfig.PortRangesToScan != null) { patched.RequestedConfig.PortRangesToScan = string.IsNullOrEmpty( request.DiscoveryConfig.PortRangesToScan.Trim()) ? null : request.DiscoveryConfig.PortRangesToScan; } if (request.DiscoveryConfig.IdleTimeBetweenScans != null) { patched.RequestedConfig.IdleTimeBetweenScans = request.DiscoveryConfig.IdleTimeBetweenScans.Value.Ticks < 0 ? null : request.DiscoveryConfig.IdleTimeBetweenScans; } if (request.DiscoveryConfig.MaxNetworkProbes != null) { patched.RequestedConfig.MaxNetworkProbes = request.DiscoveryConfig.MaxNetworkProbes <= 0 ? null : request.DiscoveryConfig.MaxNetworkProbes; } if (request.DiscoveryConfig.NetworkProbeTimeout != null) { patched.RequestedConfig.NetworkProbeTimeout = request.DiscoveryConfig.NetworkProbeTimeout.Value.Ticks <= 0 ? null : request.DiscoveryConfig.NetworkProbeTimeout; } if (request.DiscoveryConfig.MaxPortProbes != null) { patched.RequestedConfig.MaxPortProbes = request.DiscoveryConfig.MaxPortProbes <= 0 ? null : request.DiscoveryConfig.MaxPortProbes; } if (request.DiscoveryConfig.MinPortProbesPercent != null) { patched.RequestedConfig.MinPortProbesPercent = request.DiscoveryConfig.MinPortProbesPercent <= 0 || request.DiscoveryConfig.MinPortProbesPercent > 100 ? null : request.DiscoveryConfig.MinPortProbesPercent; } if (request.DiscoveryConfig.PortProbeTimeout != null) { patched.RequestedConfig.PortProbeTimeout = request.DiscoveryConfig.PortProbeTimeout.Value.Ticks <= 0 ? null : request.DiscoveryConfig.PortProbeTimeout; } if (request.DiscoveryConfig.ActivationFilter != null) { patched.RequestedConfig.ActivationFilter = request.DiscoveryConfig.ActivationFilter.SecurityMode == null && request.DiscoveryConfig.ActivationFilter.SecurityPolicies == null && request.DiscoveryConfig.ActivationFilter.TrustLists == null ? null : request.DiscoveryConfig.ActivationFilter; } } // Patch await _iothub.PatchAsync(registration.Patch( patched.ToDiscovererRegistration()), false, ct); return; } catch (ResourceOutOfDateException ex) { _logger.Debug(ex, "Retrying updating discoverer..."); continue; } } }