/// <inheritdoc/> public Task OnEndpointNewAsync(RegistryOperationContextModel context, EndpointInfoModel endpoint) { using (_metrics.TrackDuration(nameof(OnEndpointNewAsync))) { return(CheckEndpointInfoAsync(endpoint)); } }
/// <inheritdoc/> public Task OnApplicationNewAsync(RegistryOperationContextModel context, ApplicationInfoModel application) { using (_metrics.TrackDuration(nameof(OnApplicationNewAsync))) { return(CheckApplicationInfoAsync(application)); } }
/// <inheritdoc/> public async Task DeactivateEndpointAsync(string endpointId, RegistryOperationContextModel context, CancellationToken ct) { if (string.IsNullOrEmpty(endpointId)) { throw new ArgumentException(nameof(endpointId)); } context = context.Validate(); // Get existing endpoint and compare to see if we need to patch. var twin = await _iothub.GetAsync(endpointId, null, ct); if (twin.Id != endpointId) { throw new ArgumentException("Id must be same as twin to deactivate", nameof(endpointId)); } // Convert to twin registration var registration = twin.ToEntityRegistration(true) as EndpointRegistration; if (registration == null) { throw new ResourceNotFoundException( $"{endpointId} is not an endpoint registration."); } if (registration.Activated ?? false) { await DeactivateAsync(registration, context, ct); } }
/// <inheritdoc/> public Task PurgeDisabledApplicationsAsync(TimeSpan notSeenFor, RegistryOperationContextModel context, CancellationToken ct) { context = context.Validate(); // TODO: Implement correctly return(Task.CompletedTask); }
/// <summary> /// Deactivate /// </summary> /// <param name="registration"></param> /// <param name="context"></param> /// <param name="ct"></param> /// <returns></returns> private async Task DeactivateAsync(EndpointRegistration registration, RegistryOperationContextModel context, CancellationToken ct = default) { var endpoint = registration.ToServiceModel(); // Deactivate twin in twin settings - do no matter what await ClearSupervisorTwinSecretAsync(registration.DeviceId, registration.SupervisorId, ct); // Call down to supervisor to ensure deactivation is complete - do no matter what await Try.Async(() => _activator.DeactivateEndpointAsync(endpoint.Registration)); try { // Mark as deactivated if (registration.Activated ?? false) { var patch = endpoint.ToEndpointRegistration( _serializer, registration.IsDisabled); // Mark as deactivated patch.Activated = false; await _iothub.PatchAsync(registration.Patch(patch, _serializer), true); } await _broker.NotifyAllAsync(l => l.OnEndpointDeactivatedAsync(context, endpoint)); } catch (Exception ex) { _logger.Error(ex, "Failed to deactivate twin"); throw; } }
/// <inheritdoc/> public async Task RejectApplicationAsync(string applicationId, bool force, RegistryOperationContextModel context, CancellationToken ct) { context = context.Validate(); var app = await UpdateApplicationStateAsync(applicationId, ApplicationState.Rejected, s => s == ApplicationState.New || force); }
/// <summary> /// Delete all requests for the given entity /// </summary> /// <param name="entityId"></param> /// <param name="context"></param> /// <returns></returns> private async Task RemoveAllRequestsForEntityAsync(string entityId, RegistryOperationContextModel context) { string nextPageLink = null; var result = await _requests.QueryRequestsAsync( new CertificateRequestQueryRequestModel { EntityId = entityId }); while (true) { nextPageLink = result.NextPageLink; foreach (var request in result.Requests) { if (request.State != CertificateRequestState.Accepted) { await Try.Async(() => _requests.AcceptRequestAsync( request.RequestId, new VaultOperationContextModel { AuthorityId = context?.AuthorityId, Time = context?.Time ?? DateTime.UtcNow })); } } if (result.NextPageLink == null) { break; } result = await _requests.ListRequestsAsync(result.NextPageLink); } }
/// <inheritdoc/> public async Task HandleDeviceTwinEventAsync(DeviceTwinEvent ev) { if (ev.Handled) { return; } if (string.IsNullOrEmpty(ev.Twin.Id)) { return; } var type = ev.Twin.Tags.GetValueOrDefault <string>( nameof(EntityRegistration.DeviceType), null); if ((ev.Event != DeviceTwinEventType.Delete && ev.IsPatch) || string.IsNullOrEmpty(type)) { try { ev.Twin = await _iothub.GetAsync(ev.Twin.Id); ev.IsPatch = false; type = ev.Twin.Tags.GetValueOrDefault <string>( nameof(EntityRegistration.DeviceType), null); } catch (Exception ex) { _logger.Verbose(ex, "Failed to materialize twin"); } } if (IdentityType.Endpoint.EqualsIgnoreCase(type)) { var ctx = new RegistryOperationContextModel { AuthorityId = ev.AuthorityId, Time = ev.Timestamp }; switch (ev.Event) { case DeviceTwinEventType.New: break; case DeviceTwinEventType.Create: await _broker.NotifyAllAsync(l => l.OnEndpointNewAsync(ctx, ev.Twin.ToEndpointRegistration(false).ToServiceModel())); break; case DeviceTwinEventType.Update: await _broker.NotifyAllAsync(l => l.OnEndpointUpdatedAsync(ctx, ev.Twin.ToEndpointRegistration(false).ToServiceModel())); break; case DeviceTwinEventType.Delete: await _broker.NotifyAllAsync(l => l.OnEndpointDeletedAsync(ctx, ev.Twin.Id, ev.Twin.ToEndpointRegistration(false).ToServiceModel())); break; } ev.Handled = true; } }
/// <inheritdoc/> public async Task HandleDeviceTwinEventAsync(DeviceTwinEvent ev) { if (ev.Handled) { return; } if (string.IsNullOrEmpty(ev.Twin.Id) || string.IsNullOrEmpty(ev.Twin.ModuleId)) { return; } var type = ev.Twin.Properties?.Reported.GetValueOrDefault <string>( TwinProperty.Type, null); if ((ev.Event != DeviceTwinEventType.Delete && ev.IsPatch) || string.IsNullOrEmpty(type)) { try { ev.Twin = await _iothub.GetAsync(ev.Twin.Id, ev.Twin.ModuleId); ev.IsPatch = false; type = ev.Twin.Properties?.Reported?.GetValueOrDefault <string>( TwinProperty.Type, null); } catch (Exception ex) { _logger.Verbose(ex, "Failed to materialize twin"); } } if (IdentityType.Discoverer.EqualsIgnoreCase(type)) { var ctx = new RegistryOperationContextModel { AuthorityId = ev.AuthorityId, Time = ev.Timestamp }; switch (ev.Event) { case DeviceTwinEventType.New: break; case DeviceTwinEventType.Create: await _broker.NotifyAllAsync(l => l.OnDiscovererNewAsync(ctx, ev.Twin.ToDiscovererRegistration(false).ToServiceModel())); break; case DeviceTwinEventType.Update: await _broker.NotifyAllAsync(l => l.OnDiscovererUpdatedAsync(ctx, ev.Twin.ToDiscovererRegistration(false).ToServiceModel())); break; case DeviceTwinEventType.Delete: await _broker.NotifyAllAsync(l => l.OnDiscovererDeletedAsync(ctx, DiscovererModelEx.CreateDiscovererId( ev.Twin.Id, ev.Twin.ModuleId))); break; } ev.Handled = true; } }
/// <summary> /// Create endpoint event /// </summary> /// <param name="type"></param> /// <param name="context"></param> /// <param name="endpoint"></param> /// <returns></returns> private static EndpointEventModel Wrap(EndpointEventType type, RegistryOperationContextModel context, EndpointInfoModel endpoint) { return(new EndpointEventModel { EventType = type, Context = context, Endpoint = endpoint }); }
/// <summary> /// Create from service model /// </summary> /// <param name="model"></param> public RegistryOperationContextApiModel(RegistryOperationContextModel model) { if (model == null) { throw new ArgumentNullException(nameof(model)); } Time = model.Time; AuthorityId = model.AuthorityId; }
/// <summary> /// Create application event /// </summary> /// <param name="type"></param> /// <param name="context"></param> /// <param name="application"></param> /// <returns></returns> private static ApplicationEventModel Wrap(ApplicationEventType type, RegistryOperationContextModel context, ApplicationInfoModel application) { return(new ApplicationEventModel { EventType = type, Context = context, Application = application }); }
/// <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); }
/// <summary> /// Apply activation filter /// </summary> /// <param name="filter"></param> /// <param name="registration"></param> /// <param name="context"></param> /// <param name="ct"></param> /// <returns></returns> private async Task <string> ApplyActivationFilterAsync( EndpointActivationFilterModel filter, EndpointRegistration registration, RegistryOperationContextModel context, CancellationToken ct = default) { if (filter == null || registration == null) { return(null); } // TODO: Get trust list entry and validate endpoint.Certificate var mode = registration.SecurityMode ?? SecurityMode.None; if (!mode.MatchesFilter(filter.SecurityMode ?? SecurityMode.Best)) { return(null); } var policy = registration.SecurityPolicy; if (filter.SecurityPolicies != null) { if (!filter.SecurityPolicies.Any(p => p.EqualsIgnoreCase(registration.SecurityPolicy))) { return(null); } } try { // Ensure device scope for the registration before getting the secret. // Changing device's scope regenerates the secret. await EnsureDeviceScopeForRegistrationAsync(registration, ct); // Get endpoint twin secret var secret = await _iothub.GetPrimaryKeyAsync(registration.DeviceId, null, ct); var endpoint = registration.ToServiceModel(); // Try activate endpoint - if possible... await _activator.ActivateEndpointAsync(endpoint.Registration, secret, ct); // Mark in supervisor await SetSupervisorTwinSecretAsync(registration.SupervisorId, registration.DeviceId, secret, ct); registration.Activated = true; await _broker.NotifyAllAsync( l => l.OnEndpointActivatedAsync(context, endpoint)); return(secret); } catch (Exception ex) { _logger.Information(ex, "Failed activating {eeviceId} based off " + "filter. Manual activation required.", registration.DeviceId); return(null); } }
/// <summary> /// Create discoverer event /// </summary> /// <param name="type"></param> /// <param name="context"></param> /// <param name="discovererId"></param> /// <param name="discoverer"></param> /// <returns></returns> private static DiscovererEventModel Wrap(DiscovererEventType type, RegistryOperationContextModel context, string discovererId, DiscovererModel discoverer) { return(new DiscovererEventModel { EventType = type, Context = context, Id = discovererId, Discoverer = discoverer }); }
/// <summary> /// Create gateway event /// </summary> /// <param name="type"></param> /// <param name="context"></param> /// <param name="gatewayId"></param> /// <param name="gateway"></param> /// <returns></returns> private static GatewayEventModel Wrap(GatewayEventType type, RegistryOperationContextModel context, string gatewayId, GatewayModel gateway) { return(new GatewayEventModel { EventType = type, Context = context, Id = gatewayId, Gateway = gateway }); }
/// <summary> /// Create supervisor event /// </summary> /// <param name="type"></param> /// <param name="context"></param> /// <param name="supervisorId"></param> /// <param name="supervisor"></param> /// <returns></returns> private static SupervisorEventModel Wrap(SupervisorEventType type, RegistryOperationContextModel context, string supervisorId, SupervisorModel supervisor) { return(new SupervisorEventModel { EventType = type, Context = context, Id = supervisorId, Supervisor = supervisor }); }
/// <summary> /// Create publisher event /// </summary> /// <param name="type"></param> /// <param name="context"></param> /// <param name="publisherId"></param> /// <param name="publisher"></param> /// <returns></returns> private static PublisherEventModel Wrap(PublisherEventType type, RegistryOperationContextModel context, string publisherId, PublisherModel publisher) { return(new PublisherEventModel { EventType = type, Context = context, Id = publisherId, Publisher = publisher }); }
/// <inheritdoc/> public async Task ActivateEndpointAsync(string endpointId, RegistryOperationContextModel context, CancellationToken ct) { if (string.IsNullOrEmpty(endpointId)) { throw new ArgumentException(nameof(endpointId)); } context = context.Validate(); // Get existing endpoint and compare to see if we need to patch. var twin = await _iothub.GetAsync(endpointId, null, ct); if (twin.Id != endpointId) { throw new ArgumentException("Id must be same as twin to activate", nameof(endpointId)); } // Convert to twin registration var registration = twin.ToEntityRegistration(true) as EndpointRegistration; if (registration == null) { throw new ResourceNotFoundException( $"{endpointId} is not an endpoint registration."); } if (registration.IsDisabled ?? false) { throw new ResourceInvalidStateException( $"{endpointId} is disabled - cannot activate"); } if (!(registration.Activated ?? false)) { // Activate if not yet activated try { if (string.IsNullOrEmpty(registration.SupervisorId)) { throw new ArgumentException( $"Twin {endpointId} not registered with a supervisor."); } await ActivateAsync(registration, context, ct); } catch (Exception ex) { // Try other supervisors as candidates if (!await ActivateAsync(registration, null, context, ct)) { throw ex; } } } }
/// <summary> /// Create from service model /// </summary> /// <param name="model"></param> public static RegistryOperationContextApiModel ToApiModel( this RegistryOperationContextModel model) { if (model == null) { return(null); } return(new RegistryOperationContextApiModel { AuthorityId = model.AuthorityId, Time = model.Time }); }
/// <inheritdoc/> public async Task DisableApplicationAsync(string applicationId, RegistryOperationContextModel context, CancellationToken ct) { context = context.Validate(); var app = await _database.UpdateAsync(applicationId, (application, disabled) => { // Disable application if (!(disabled ?? false)) { application.NotSeenSince = DateTime.UtcNow; application.Updated = context; return(true, true); }
/// <inheritdoc/> public async Task ApproveApplicationAsync(string applicationId, bool force, RegistryOperationContextModel context, CancellationToken ct) { context = context.Validate(); var app = await _database.UpdateAsync(applicationId, (application, disabled) => { if (disabled ?? false) { throw new ResourceInvalidStateException("The application is disabled."); } if (application.State == ApplicationState.Approved) { return(null, null); }
/// <summary> /// Activate /// </summary> /// <param name="registration"></param> /// <param name="context"></param> /// <param name="ct"></param> /// <returns></returns> private async Task ActivateAsync(EndpointRegistration registration, RegistryOperationContextModel context, CancellationToken ct = default) { // Ensure device scope for the registration before getting the secret. // Changing device's scope regenerates the secret. await EnsureDeviceScopeForRegistrationAsync(registration, ct); // Update supervisor settings var secret = await _iothub.GetPrimaryKeyAsync(registration.DeviceId, null, ct); var endpoint = registration.ToServiceModel(); try { // Call down to supervisor to activate - this can fail await _activator.ActivateEndpointAsync(endpoint.Registration, secret, ct); // Update supervisor desired properties await SetSupervisorTwinSecretAsync(registration.SupervisorId, registration.DeviceId, secret, ct); if (!(registration.Activated ?? false)) { // Update twin activation status in twin settings var patch = endpoint.ToEndpointRegistration(_serializer, registration.IsDisabled); patch.Activated = true; // Mark registration as activated await _iothub.PatchAsync(registration.Patch(patch, _serializer), true, ct); } await _broker.NotifyAllAsync(l => l.OnEndpointActivatedAsync(context, endpoint)); } catch (Exception ex) { // Undo activation await Try.Async(() => _activator.DeactivateEndpointAsync( endpoint.Registration)); await Try.Async(() => ClearSupervisorTwinSecretAsync( registration.DeviceId, registration.SupervisorId)); _logger.Error(ex, "Failed to activate twin"); throw; } }
/// <inheritdoc/> public async Task OnApplicationEnabledAsync(RegistryOperationContextModel context, ApplicationInfoModel application) { var endpoints = await GetEndpointsAsync(application.ApplicationId, true); foreach (var registration in endpoints) { // Enable if disabled if (!(registration.IsDisabled ?? false)) { continue; } try { var endpoint = registration.ToServiceModel(); endpoint.NotSeenSince = null; var update = endpoint.ToEndpointRegistration(_serializer, false); await _iothub.PatchAsync(registration.Patch(update, _serializer)); await _broker.NotifyAllAsync( l => l.OnEndpointEnabledAsync(context, endpoint)); } catch (Exception ex) { _logger.Error(ex, "Failed re-enabling endpoint {id}", registration.Id); continue; } // Activate if it was activated before if (!(registration.Activated ?? false)) { continue; // No need to re-activate on enable } try { await ActivateAsync(registration, context); } catch (Exception ex) { _logger.Error(ex, "Failed activating re-enabled endpoint {id}", registration.Id); } } }
/// <inheritdoc/> public async Task OnApplicationDisabledAsync(RegistryOperationContextModel context, ApplicationInfoModel application) { // Disable endpoints var endpoints = await GetEndpointsAsync(application.ApplicationId, true); foreach (var registration in endpoints) { if (registration.Activated ?? false) { try { registration.Activated = false; // Prevent patching... await DeactivateAsync(registration, context); registration.Activated = true; // reset activated state } catch (Exception ex) { _logger.Error(ex, "Failed deactivating disabled endpoint {id}", registration.Id); } } // Disable if enabled if (!(registration.IsDisabled ?? false)) { try { var endpoint = registration.ToServiceModel(); endpoint.NotSeenSince = DateTime.UtcNow; var update = endpoint.ToEndpointRegistration(_serializer, true); await _iothub.PatchAsync(registration.Patch(update, _serializer)); await _broker.NotifyAllAsync( l => l.OnEndpointDisabledAsync(context, endpoint)); } catch (Exception ex) { _logger.Error(ex, "Failed disabling endpoint {id}", registration.Id); } } } }
/// <inheritdoc/> public async Task OnApplicationDeletedAsync(RegistryOperationContextModel context, ApplicationInfoModel application) { // Get all endpoint registrations and for each one, call delete, if failure, // stop half way and throw and do not complete. var endpoints = await GetEndpointsAsync(application.ApplicationId, true); foreach (var registration in endpoints) { var endpoint = registration.ToServiceModel(); try { registration.Activated = false; // Prevents patching since we delete below. await DeactivateAsync(registration, context); } catch (Exception ex) { _logger.Error(ex, "Failed deleting registered endpoint endpoint {id}", registration.Id); } await _iothub.DeleteAsync(registration.DeviceId); await _broker.NotifyAllAsync(l => l.OnEndpointDeletedAsync(context, endpoint)); } }
/// <inheritdoc/> public async Task UnregisterApplicationAsync(string applicationId, RegistryOperationContextModel context, CancellationToken ct) { context = context.Validate(); if (string.IsNullOrEmpty(applicationId)) { throw new ArgumentNullException(nameof(applicationId), "The application id must be provided"); } while (true) { var document = await _applications.FindAsync <ApplicationDocument>(applicationId); if (document == null) { throw new ResourceNotFoundException( "A record with the specified application id does not exist."); } var application = document.Value.Clone(); try { // Try delete await _applications.DeleteAsync(document); // Success -- Notify others to clean up var app = document.Value.ToServiceModel(); // Try free record id await Try.Async(() => _index.FreeAsync(document.Value.ID)); break; } catch (ResourceOutOfDateException) { _logger.Verbose("Retry unregister application operation."); continue; } } }
/// <inheritdoc/> public Task OnSupervisorUpdatedAsync(RegistryOperationContextModel context, SupervisorModel supervisor) { return(_bus.PublishAsync(Wrap(SupervisorEventType.Updated, context, supervisor.Id, supervisor))); }
/// <inheritdoc/> public Task OnSupervisorDeletedAsync(RegistryOperationContextModel context, string supervisorId) { return(_bus.PublishAsync(Wrap(SupervisorEventType.Deleted, context, supervisorId, null))); }
/// <inheritdoc/> public Task OnApplicationUpdatedAsync(RegistryOperationContextModel context, ApplicationInfoModel application) { return(Task.CompletedTask); }