Openstack.Net SDK cannot access service with regio

2020-05-01 08:19发布

问题:

Using our own hardware, we've installed vanilla openstack with all components however I am having problems accessing services other than the Identity due to a region issue. The code used is as follows called with the admin account and admin tennant we created...

    public static void TestAccess(string userName, string password, string projectName, string projectId)
    {
        try
        {
            Uri baseUrl = new Uri(URL_IDENTITY);

            CloudIdentityWithProject projectCloudId = new CloudIdentityWithProject();
            projectCloudId.Username = userName;
            projectCloudId.Password = password;
            projectCloudId.ProjectName = projectName;
            projectCloudId.ProjectId = new ProjectId(projectId);

            OpenStackIdentityProvider idProvider = new OpenStackIdentityProvider(baseUrl, projectCloudId);
            UserAccess userAccess = idProvider.Authenticate(projectCloudId);
            IEnumerable<ExtendedEndpoint> eps = idProvider.ListEndpoints(userAccess.Token.Id);

            string reg = idProvider.DefaultRegion; // This is null

            ServiceCatalog[] scs = userAccess.ServiceCatalog;

            // Get the list of regions
            regionList = new List<string>();
            foreach (ServiceCatalog sc in scs)
            {
                foreach (Endpoint ep in sc.Endpoints)
                {
                    regionList.Add(ep.Region); // This is 'regionOne' in every case
                }
            }

            // Try stuff...
            foreach(string region in regionList.Distinct())
            {
                // Get a list of containers
                CloudFilesProvider cfp = new CloudFilesProvider(projectCloudId, idProvider);
                // THIS LINE FAILS
                IEnumerable<Container> listOfContainers = cfp.ListContainers(region: region);
                foreach (Container ctnr in listOfContainers)
                {
                    Console.WriteLine("Container: {0}", ctnr.Name);
                }

                CloudNetworksProvider cnp = new CloudNetworksProvider(identity: null, identityProvider: idProvider);
                IEnumerable<CloudNetwork> networks = cnp.ListNetworks(identity: null, region: region);
                foreach (CloudNetwork network in networks)
                {
                    Console.WriteLine("Network[{0}] name: {1}", networkCount, network.Label);
                    Console.WriteLine("Network[{0}] Id: {1}", networkCount, network.Id);
                    ++networkCount;
                }

                Console.WriteLine("{0} networks listed.", networkCount);
            }
        }
        catch(Exception ex)
        {
            throw;
        }
    }

The code fails at the call to ListContainers(region: region) with the error... 'The user does not have access to the requested service or region' where as if I don't specify a region the error is simply 'No region was provided, the service does not provide a region-independent endpoint, and there is no default region set for the user's account'

We are only accessing our internal network at the moment so regions aren't important to us yet...

Also of note is that when making a call to...

CloudNetwork detail = cnp.ShowNetwork(networkGuid, "regionOne");

of a network I can see returns the error 'The item was not found or does not exist.'

Help and advice much appreciated.

回答1:

I managed to extend the functionality of the Openstack.Net SDK fairly simply. The code below extends it to include various functions for Tenant/Project manipulations...

Firstly, create a NewTenant container that will be used to pass data to and from the webservices, I've put it in the same namespace as the others...

using Newtonsoft.Json;

namespace net.openstack.Core.Domain
{

    [JsonObject(MemberSerialization.OptIn)]
    public class NewTenant
    {
        /// <summary>
        /// Gets the ID for the new user.
        /// <note type="warning">The value of this property is not defined. Do not use.</note>
        /// </summary>
        [JsonProperty("id", DefaultValueHandling = DefaultValueHandling.Include)]
        public string Id { get; private set; }

        [JsonProperty("name")]
        public string Name { get; private set; }

        [JsonProperty("description")]
        public string Description { get; private set; }

        [JsonProperty("enabled")]
        public bool Enabled { get; private set; }

        public NewTenant(string name, string description, bool enabled = true)
        {
            Name = name;
            Description = description;
            Enabled = enabled;
        }
    }
}

Now we can create any new Request classes for posting the data...

using System;
using Newtonsoft.Json;
using net.openstack.Core.Domain;

namespace net.openstack.Core.Request
{

    [JsonObject(MemberSerialization.OptIn)]
    internal class AddTenantRequest
    {
        [JsonProperty("tenant")]
        public NewTenant Tenant { get; private set; }

        public AddTenantRequest(NewTenant tenant)
        {
            if (tenant == null)
                throw new ArgumentNullException("tenant");

            Tenant = tenant;
        }
    }
}

Now create the Response objects for the requests to help retrieve the data

using net.openstack.Core.Domain;
using Newtonsoft.Json;

namespace net.openstack.Core.Response
{
    [JsonObject(MemberSerialization.OptIn)]
    internal class NewTenantResponse
    {
        [JsonProperty("tenant")]
        public NewTenant NewTenant { get; private set; }
    }

    [JsonObject(MemberSerialization.OptIn)]
    internal class TenantResponse
    {
        [JsonProperty("tenant")]
        public Tenant Tenant { get; private set; }
    }
}

Now we can create a class that inherits from OpenStackIdentityProvider with the additional functionality for Tenant/Project manipulation that we want...

using System;
using System.Net;
using JSIStudios.SimpleRESTServices.Client;
using net.openstack.Core.Domain;
using net.openstack.Core.Request;
using net.openstack.Core.Response;

namespace net.openstack.Core.Providers
{
    public class ExtendedOpenStackIdentityProvider : OpenStackIdentityProvider
    {
        public ExtendedOpenStackIdentityProvider(Uri urlBase)
            : base(urlBase)
        {
        }

        public ExtendedOpenStackIdentityProvider(Uri urlBase, CloudIdentity identity)
            : base(urlBase, identity)
        {
        }

        public ExtendedOpenStackIdentityProvider(Uri urlBase, JSIStudios.SimpleRESTServices.Client.IRestService restService, net.openstack.Core.Caching.ICache<UserAccess> tokenCache)
            : base(urlBase, restService, tokenCache)
        {
        }

        public ExtendedOpenStackIdentityProvider(Uri urlBase, CloudIdentity identity, JSIStudios.SimpleRESTServices.Client.IRestService restService, net.openstack.Core.Caching.ICache<UserAccess> tokenCache)
            : base(urlBase, identity, restService, tokenCache)
        {
        }

        public NewTenant AddTenant(NewTenant tenant, CloudIdentity identity)
        {
            if (tenant == null)
                throw new ArgumentNullException("tenant");
            if (string.IsNullOrEmpty(tenant.Name))
                throw new ArgumentException("tenant.Name cannot be null or empty");
            if (tenant.Id != null)
                throw new InvalidOperationException("tenant.Id must be null");

            CheckIdentity(identity);

            var response = ExecuteRESTRequest<NewTenantResponse>(identity, new Uri(UrlBase, "/v2.0/tenants"), HttpMethod.POST, new AddTenantRequest(tenant));

            if (response == null || response.Data == null)
                return null;

            return response.Data.NewTenant;
        }

        public Tenant GetTenant(string tenantId, CloudIdentity identity)
        {
            if (tenantId == null)
                throw new ArgumentNullException("tenantId");

            CheckIdentity(identity);

            var urlPath = string.Format("v2.0/tenants/{0}", tenantId);

            var response = ExecuteRESTRequest<TenantResponse>(identity, new Uri(UrlBase, urlPath), HttpMethod.GET);

            if (response == null || response.Data == null)
                return null;

            return response.Data.Tenant;
        }

        public bool DeleteTenant(string tenantId, CloudIdentity identity)
        {
            if (tenantId == null)
                throw new ArgumentNullException("tenantId");
            if (string.IsNullOrEmpty(tenantId))
                throw new ArgumentException("tenantId cannot be empty");
            CheckIdentity(identity);

            var urlPath = string.Format("v2.0/tenants/{0}", tenantId);
            var response = ExecuteRESTRequest(identity, new Uri(UrlBase, urlPath), HttpMethod.DELETE);

            if (response != null && response.StatusCode == HttpStatusCode.NoContent)
                return true;

            return false;
        }

        public bool AddTenantUserRole(string tenantId, string userId, string roleId, CloudIdentity identity)
        {
            if (tenantId == null)
                throw new ArgumentNullException("tenantId");
            if (string.IsNullOrEmpty(tenantId))
                throw new ArgumentException("tenantId cannot be empty");
            if (userId == null)
                throw new ArgumentNullException("userId");
            if (string.IsNullOrEmpty(userId))
                throw new ArgumentException("userId cannot be empty");
            if (roleId == null)
                throw new ArgumentNullException("roleId");
            if (string.IsNullOrEmpty(roleId))
                throw new ArgumentException("roleId cannot be empty");

            CheckIdentity(identity);

            var urlPath = string.Format("v2.0/tenants/{0}/users/{1}/roles/OS-KSADM/{2}", tenantId, userId, roleId);
            var response = ExecuteRESTRequest(identity, new Uri(UrlBase, urlPath), HttpMethod.PUT);

            if (response != null && response.StatusCode == HttpStatusCode.NoContent)
                return true;

            return false;
        }
    }
}

I imagine that this functionality will appear in the GitHub version soon, but if not I hope it's useful.