/// <inheritdoc/> /// <remarks> /// This method updates the <paramref name="serviceType"/> and/or <paramref name="serviceName"/> values for /// cases where "vanilla" OpenStack values were specified but Rackspace exposes the compatible services under /// different names in the service catalog. /// </remarks> protected override Uri GetBaseAddressImpl(Access access, string serviceType, string serviceName, string region, bool internalAddress) { if (string.IsNullOrEmpty(serviceName)) { switch (serviceType) { case "cdn": serviceName = "rackCDN"; serviceType = "rax:cdn"; break; case "compute": serviceName = "cloudServersOpenStack"; break; case "volume": serviceName = "cloudBlockStorage"; break; case "object-store": serviceName = "cloudFiles"; break; case "database": serviceType = "rax:database"; serviceName = "cloudDatabases"; break; case "queuing": serviceType = "rax:queues"; serviceName = "cloudQueues"; break; } } return base.GetBaseAddressImpl(access, serviceType, serviceName, region, internalAddress); }
/// <inheritdoc/> /// <remarks> /// This implementation returns <paramref name="region"/> if it is non-<see langword="null"/>. If /// <paramref name="region"/> is <see langword="null"/>, the default region for the specified /// <see cref="Access"/> is returned if available; otherwise, this method returns <see langword="null"/>. /// </remarks> protected override string GetEffectiveRegion(Access access, string region) { string effectiveRegion = base.GetEffectiveRegion(access, region); if (effectiveRegion == null) effectiveRegion = access.User.GetDefaultRegion(); return effectiveRegion; }
/// <summary> /// This method provides the core implementation of <see cref="GetBaseAddressAsync"/> after the /// <see cref="OpenStack.Services.Identity.V2.Access"/> details are obtained from the Identity Service. /// </summary> /// <param name="access">An <see cref="OpenStack.Services.Identity.V2.Access"/> object containing the details /// for the authenticated user.</param> /// <param name="serviceType">The service type to locate.</param> /// <param name="serviceName">The preferred name of the service.</param> /// <param name="region">The preferred region for the service. This method calls /// <see cref="GetEffectiveRegion"/> with this value to obtain the actual region to consider for this /// algorithm.</param> /// <param name="internalAddress"> /// <para><see langword="true"/> to return a base address for accessing the service over a local network.</para> /// <para>-or-</para> /// <para><see langword="false"/> to return a base address for accessing the service over a public network (the /// Internet).</para></param> /// <returns>A <see cref="Uri"/> containing the absolute base address for accessing the service.</returns> /// <exception cref="ArgumentNullException"> /// <para>If <paramref name="access"/> is <see langword="null"/>.</para> /// <para>-or-</para> /// <para>If <paramref name="serviceType"/> is <see langword="null"/>.</para> /// </exception> /// <exception cref="ArgumentException">If <paramref name="serviceType"/> is empty.</exception> protected virtual Uri GetBaseAddressImpl(Access access, string serviceType, string serviceName, string region, bool internalAddress) { if (access == null) throw new ArgumentNullException("access"); if (serviceType == null) throw new ArgumentNullException("serviceType"); if (string.IsNullOrEmpty(serviceType)) throw new ArgumentException("serviceType cannot be empty", "serviceType"); if (access.ServiceCatalog == null) throw new InvalidOperationException("The authentication information provided by the Identity Service did not include a service catalog."); List<ServiceCatalogEntry> services = access.ServiceCatalog.Where(sc => string.Equals(sc.Type, serviceType, StringComparison.OrdinalIgnoreCase)).ToList(); if (services.Count == 0) throw new InvalidOperationException(string.Format("The service catalog provided by the Identity Service did not include any service with the type '{0}'.", serviceType)); if (serviceName != null) { // If any service matches the preferred name, filter the entire list. Otherwise, simply ignore the // preferred name and continue. List<ServiceCatalogEntry> namedServices = services.Where(sc => string.Equals(sc.Name, serviceName, StringComparison.OrdinalIgnoreCase)).ToList(); if (namedServices.Count > 0) services = namedServices; } // Treat each endpoint individually for the purpose of endpoint selection. List<Tuple<ServiceCatalogEntry, Endpoint>> endpoints = services.SelectMany(service => service.Endpoints.Select(endpoint => Tuple.Create(service, endpoint))).ToList(); string effectiveRegion = GetEffectiveRegion(access, region); // Locate all endpoints in the effective region. (Note that null and empty are equivalent for regions.) List<Tuple<ServiceCatalogEntry, Endpoint>> regionEndpoints = endpoints.Where(i => string.Equals(i.Item2.Region ?? string.Empty, effectiveRegion ?? string.Empty, StringComparison.OrdinalIgnoreCase)).ToList(); // Use filtered results if possible, otherwise only consider "global" endpoints. if (regionEndpoints.Count > 0) endpoints = regionEndpoints; else endpoints.RemoveAll(i => !string.IsNullOrEmpty(i.Item2.Region)); if (effectiveRegion == null && !endpoints.Any()) throw new InvalidOperationException("No region was provided, no default region is available for the current credentials, and the service does not provide a region-independent endpoint."); Tuple<ServiceCatalogEntry, Endpoint> serviceEndpoint = endpoints.FirstOrDefault(); if (internalAddress) serviceEndpoint = endpoints.FirstOrDefault(i => i.Item2.InternalUrl != null); else serviceEndpoint = endpoints.FirstOrDefault(i => i.Item2.PublicUrl != null); if (serviceEndpoint == null) throw new InvalidOperationException("No endpoint matching the specified parameters was located in the service catalog."); Uri baseAddress; if (internalAddress) baseAddress = serviceEndpoint.Item2.InternalUrl; else baseAddress = serviceEndpoint.Item2.PublicUrl; Uri adjustedBaseAddress = FilterBaseAddress(baseAddress); return adjustedBaseAddress; }
/// <summary> /// Gets the effective region to use for locating services in the service catalog, for the specified /// <see cref="OpenStack.Services.Identity.V2.Access"/> information and preferred <paramref name="region"/>. /// </summary> /// <remarks> /// The default implementation simply returns <paramref name="region"/>. Specific vendors may extend this /// functionality to provide a default value or other region selections as appropriate for their users and /// service offerings. /// </remarks> /// <param name="access">The <see cref="OpenStack.Services.Identity.V2.Access"/> object providing details for /// the authenticated user.</param> /// <param name="region">The preferred region, as specified in the call to /// <see cref="GetBaseAddressAsync"/>.</param> /// <returns>The effective region to use for service location in <see cref="GetBaseAddressImpl"/>.</returns> /// <exception cref="ArgumentNullException">If <paramref name="access"/> is <see langword="null"/>.</exception> protected virtual string GetEffectiveRegion(Access access, string region) { if (access == null) throw new ArgumentNullException("access"); return region; }
/// <summary> /// Authenticates the credentials provided in <see cref="AuthenticationRequest"/> with the OpenStack Identity /// Service V2. /// </summary> /// <remarks> /// <para>This method caches the authentication result, and returns the cached result of a previous /// authentication request when possible to avoid unnecessary calls to the Identity API. If a cached /// authentication result is available but has either expired or will expire soon (see /// <see cref="ExpirationOverlap"/>), the cached result is discarded and the credentials are re-authenticated /// with the Identity Service.</para> /// </remarks> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param> /// <returns> /// A <see cref="Task"/> representing the asynchronous operation. When the task completes successfully, the /// <see cref="Task{TResult}.Result"/> property will contain an <see cref="Access"/> instance providing the /// authentication result. /// </returns> /// <exception cref="HttpWebException"> /// If an error occurs during an HTTP request as part of authenticating with the Identity API. /// </exception> protected virtual Task<Access> AuthenticateAsync(CancellationToken cancellationToken) { Access access = Access; if (access != null && access.Token != null) { Token token = access.Token; // Note: this code uses lifting to null to cover the case where token.ExpiresAt is null DateTimeOffset? effectiveExpiration = token.ExpiresAt - ExpirationOverlap; if (effectiveExpiration > DateTimeOffset.Now) return CompletedTask.FromResult(access); } return IdentityService.AuthenticateAsync(AuthenticationRequest, cancellationToken) .Select( task => { _access = task.Result; return task.Result; }); }