Azure / azure-libraries-for-net

Azure libraries for .Net
MIT License
380 stars 192 forks source link

[BUG] The resource type could not be found in the namespace 'Microsoft.Sql' for api version '2019-06-01-preview' #1005

Open Rookian opened 4 years ago

Rookian commented 4 years ago

Describe the bug I created a SQL Azure database server with a database manually and also with code using the SDK.

I have some code snippet that tags all resources within a resource group. See https://github.com/Azure/azure-libraries-for-net/issues/978#issuecomment-588261592

When I try to receive the resource for the Azure database, I get the following errror:

The resource type could not be found in the namespace 'Microsoft.Sql' for api version '2019-06-01-preview'

Failing code:

var res = await _genericResources.GetByIdAsync(resource.Id);

Expected behavior It should be possible to retrieve and tag Azure Sql database resources.

Setup:

yaohaizh commented 4 years ago

@xccc-msft please take a look at this issue to see whether we support the case. //cc @yungezz

xseeseesee commented 4 years ago

@Rookian This might be caused by the restriction on different versions from SQL service side. We will investigate if we could pick api version in better manner from SDK side. For now, you might want to use below snippet and retry:

string resourceProvider = ResourceUtils.ResourceProviderFromResourceId(id);
var provider = resourceManager.Providers.GetByName(resourceProvider);

Then follow same logic but try the other versions instead of picking the first one.

Rookian commented 4 years ago

@xccc-msft I tried all versions with no success.

Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2019-06-01-preview. Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2018-06-01-preview. Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2017-10-01-preview.
Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2017-03-01-preview. Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2015-05-01-preview. Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2015-01-01. Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2014-04-01-preview. Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2014-04-01. Could not get resource .../providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb with api version 2014-01-01.

Code:

private async Task<IGenericResource> GetResourceById(IGenericResource resource)
{
    try
    {
        var res = await _genericResources.GetByIdAsync(resource.Id);
        return res;
    }
    catch (Exception)
    {
        var resourceProvider = ResourceUtils.ResourceProviderFromResourceId(resource.Id);
        var provider = _genericResources.Manager.Providers.GetByName(resourceProvider);
        var apiVersions = GetApiVersions(resource.Id, provider);
        foreach (var apiVersion in apiVersions)
        {
            try
            {
                var res = await _genericResources.GetByIdAsync(resource.Id, apiVersion);
                return res;
            }
            catch (Exception)
            {
                ConsoleEx.WriteWarningLine($"Could not get resource {resource.Id} with api version {apiVersion}.");
            }
        }
        throw new Exception($"Could not get resource based on id {resource.Id}.");
    }
}

private static IList<string> GetApiVersions(string id, IProvider provider)
{
    var resourceType = ResourceUtils.ResourceTypeForApiVersion(id);
    if (string.IsNullOrEmpty(resourceType))
    {
        return null;
    }

    foreach (var providerResourceType in provider.ResourceTypes)
    {
        if (string.Equals(resourceType, providerResourceType.ResourceType, StringComparison.OrdinalIgnoreCase)
            && providerResourceType.ApiVersions != null
            && providerResourceType.ApiVersions.Count > 0)
        {
            return providerResourceType.ApiVersions;
        }
    }

    return new List<string>();
}
ghost commented 4 years ago

Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @armleads-azure.

xseeseesee commented 4 years ago

@Rookian Thanks for the update. I just add a label so we can get help from service end to check this.

rthorn17 commented 4 years ago

SQL team needs to investigate concerning their resource provider manifest and api version

jamestao commented 4 years ago

Not sure if the problem is in the SDK or the input, but from telemetry the generated url is incorrect: https://management.azure.com:443/subscriptions/<subid>/resourcegroups/<rgname>/providers/Microsoft.Sql/subscriptions/<subid>/resourceGroups/<rgname>/providers/Microsoft.Sql/servers/alextestsrc/databases/alextestdb?api-version=2019-06-01-preview

Notice that the subscriptions, resourcegroups and providers segments are repeated in the request url.

xseeseesee commented 4 years ago

@jamestao Thanks for sharing the observation. @Rookian I just commit changes to fix this. Before we release, you might use below code snippet as a workaround.

var resourceGroupName = ResourceUtils.GroupFromResourceId(id);
var resourceProviderNamespace = ResourceUtils.ResourceProviderFromResourceId(id);
//key step for parent resource path
var parentResourcePath = ResourceUtils.ParentResourcePathFromResourceId(id);
if (!string.IsNullOrEmpty(parentResourcePath))
{
    parentResourcePath = ResourceUtils.NameFromResourceId(parentResourcePath);
}
var resourceType = ResourceUtils.ResourceTypeForResourceId(id);
var resourceName = ResourceUtils.NameFromResourceId(id);
var res = await _genericResources.GetAsync(resourceGroupName, resourceProviderNamespace, parentResourcePath, resourceType, resourceName, apiVersion);
return res;
tony6636 commented 4 years ago

Hello guys, the issue remains:

Setup:

The code fails at the line where the Tags are being updated:

await res.Update()
         .WithApiVersion(res.ApiVersion)
         .WithProperties(res.Properties)
         .WithTags(...)
         .ApplyAsync();

see https://github.com/Azure/azure-libraries-for-net/issues/978#issuecomment-588261592. I'm trying to set the Tags for the SQL Server Database.

For the SQL Server's Instance Resource, the code works fine.

resource ID: /subscriptions/(id)/resourceGroups/(id)/providers/Microsoft.Sql/servers/(sql_server_instance_id)/databases/(sql_server_db_name)

The workaround here that worked for me is to update Tags with using the SqlManagementClient class:

AzureCredentials credentials = ...

RestClient restClient = RestClient.Configure()
    .WithEnvironment(AzureEnvironment.AzureGlobalCloud)
    .WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
    .WithCredentials(credentials)
    .Build();

_client = new SqlManagementClient(restClient)
{
    SubscriptionId = ...
};

if (!string.IsNullOrWhiteSpace(resource.ParentResourceId))
{
    string databaseName = ResourceUtils.NameFromResourceId(resource.Id);
    string serverName = ResourceUtils.NameFromResourceId(resource.ParentResourceId);

    await _client.Databases.UpdateAsync(resourceGroup,
        serverName,
        databaseName,
        new DatabaseUpdateInner
        {
            Tags = new Dictionary<string, string>() { ... }
        });
}

Please apply the fix in the Fluent API approach

xseeseesee commented 4 years ago

@tony6636 Before you call res.Update(), how did you get the var res?

tony6636 commented 4 years ago

@tony6636 Before you call res.Update(), how did you get the var res?

as mentioned, in the same was way as described here https://github.com/Azure/azure-libraries-for-net/issues/978#issuecomment-588261592

xseeseesee commented 4 years ago

@tony6636 Thanks for confirmation. You need to insert one line as below:

await res.Update()
         .WithApiVersion(res.ApiVersion)
         .WithProperties(res.Properties)
         // For resource with parent, insert below line
         .WithParentResource(ResourceUtils.ParentRelativePathFromResourceId(res.Id))
         .WithTags(...)
         .ApplyAsync();
tony6636 commented 4 years ago

@tony6636 Thanks for confirmation. You need to insert one line as below:

await res.Update()
         .WithApiVersion(res.ApiVersion)
         .WithProperties(res.Properties)
         // For resource with parent, insert below line
         .WithParentResource(ResourceUtils.ParentRelativePathFromResourceId(res.Id))
         .WithTags(...)
         .ApplyAsync();

Thanks, I've checked that approach and I'mgetting the error message like :

The Resource 'Microsoft.Sql/servers/(server-name)/databases/(db-name)' under resource group '(resource-group-name)' was not found.

physically that resource exists in that resource group

xseeseesee commented 4 years ago

@tony6636 I cannot reproduce this from my end. For me, I could update tags for the SQL database via the code snippet I shared after creating it.

Can you please capture the request and verify what's the request Uri? Or just check what value you passed into withParentResource(value)? Another option, you may verify via genericResources.checkExistenceById(id).

Rookian commented 4 years ago

I still get an error without the solution from @xccc-msft :

Microsoft.Rest.Azure.CloudException: The resource type could not be found in the namespace 'Microsoft.Sql' for api version '2020-02-02-preview'.

With the solution from @xccc-msft, I also get an error:

Microsoft.Rest.Azure.CloudException: Long running operation failed with status 'Failed'. Additional Info:''System' is not a valid database edition in this version of SQL Server.' at Microsoft.Rest.ClientRuntime.Azure.LRO.AzureLRO2.CheckForErrors() at Microsoft.Rest.ClientRuntime.Azure.LRO.PutLRO2.CheckForErrors() at Microsoft.Rest.ClientRuntime.Azure.LRO.AzureLRO2.StartPollingAsync() at Microsoft.Rest.ClientRuntime.Azure.LRO.AzureLRO2.BeginLROAsync() at Microsoft.Rest.Azure.AzureClientExtensions.GetLongRunningOperationResultAsync[TBody,THeader](IAzureClient client, AzureOperationResponse2 response, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Rest.Azure.AzureClientExtensions.GetLongRunningOperationResultAsync[TBody](IAzureClient client, AzureOperationResponse1 response, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Rest.Azure.AzureClientExtensions.GetPutOrPatchOperationResultAsync[TBody](IAzureClient client, AzureOperationResponse1 response, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.ResourcesOperations.CreateOrUpdateWithHttpMessagesAsync(String resourceGroupName, String resourceProviderNamespace, String parentResourcePath, String resourceType, String resourceName, String apiVersion, GenericResourceInner parameters, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.ResourcesOperationsExtensions.CreateOrUpdateAsync(IResourcesOperations operations, String resourceGroupName, String resourceProviderNamespace, String parentResourcePath, String resourceType, String resourceName, String apiVersion, GenericResourceInner parameters, CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.GenericResourceImpl.CreateResourceAsync(CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.Core.ResourceActions.Creatable4.Microsoft.Azure.Management.ResourceManager.Fluent.Core.ResourceActions.IResourceCreator.CreateResourceAsync(CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.Core.DAG.CreatorTaskItem1.ExecuteAsync(CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.Core.DAG.TaskGroupBase1.ExecuteNodeTaskAsync(DAGNode1 node, CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.Core.ResourceActions.CreatableUpdatable5.ApplyAsync(CancellationToken cancellationToken, Boolean multiThreaded)

//cc @rthorn17

xseeseesee commented 4 years ago

@Rookian It looks your long-running operation failed when polling status. Can you please retry again?

cc: @jamestao Do you know what the info means by Additional Info:''System' is not a valid database edition in this version of SQL Server.'?

Rookian commented 4 years ago

@Rookian It looks your long-running operation failed when polling status. Can you please retry again?

cc: @jamestao Do you know what the info means by Additional Info:''System' is not a valid database edition in this version of SQL Server.'?

It is not working.

Additional Info:''System' is not a valid database edition in this version of SQL Server.'?

This means that the database edition is unfortunately not set when retrieving the Azure resource. If you now update the resource you will get this exception. So this is a bug in my opinion.

xseeseesee commented 4 years ago

@Rookian Can you please try with _genericResources.Inner.[operation] to see if it is SDK issue or service issue? Thanks.

Rookian commented 4 years ago

@Rookian Can you please try with _genericResources.Inner.[operation] to see if it is SDK issue or service issue? Thanks.

_genericResources.Inner has no operations available.

_genericResources.Inner.Properties or _genericResources.Properties contains value:

{[currentSku, {{
  "name": "System",
  "tier": "System",
  "capacity": 0
}}]}

I created the database with

 .WithEdition(DatabaseEdition.Standard)
 .WithServiceObjective(ServiceObjectiveName.S0)
xseeseesee commented 4 years ago

@Rookian The code you shared before was var res = await _genericResources.GetByIdAsync(resource.Id); I would suggest you to try _genericResources.Inner.[operation].

Rookian commented 4 years ago
resource.Inner.Tags = Tags;
await _genericResources.Inner.UpdateAsync(
     resourceGroup.Name, resource.ResourceProviderNamespace,
     ResourceUtils.ParentRelativePathFromResourceId(res.Id), res.ResourceType,
     res.Name, res.ApiVersion, resource.Inner);

Also fails with:

Microsoft.Rest.Azure.CloudException: Long running operation failed with status 'Failed'. Additional Info:''System' is not a valid database edition in this version of SQL Server.' at Microsoft.Rest.ClientRuntime.Azure.LRO.AzureLRO2.CheckForErrors() at Microsoft.Rest.ClientRuntime.Azure.LRO.AzureLRO2.StartPollingAsync() at Microsoft.Rest.ClientRuntime.Azure.LRO.AzureLRO2.BeginLROAsync() at Microsoft.Rest.Azure.AzureClientExtensions.GetLongRunningOperationResultAsync[TBody,THeader](IAzureClient client, >AzureOperationResponse2 response, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Rest.Azure.AzureClientExtensions.GetLongRunningOperationResultAsync[TBody](IAzureClient client, AzureOperationResponse1 response, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Rest.Azure.AzureClientExtensions.GetPutOrPatchOperationResultAsync[TBody](IAzureClient client, AzureOperationResponse1 response, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.ResourcesOperations.UpdateWithHttpMessagesAsync(String resourceGroupName, String resourceProviderNamespace, String parentResourcePath, String resourceType, String resourceName, String apiVersion, GenericResourceInner parameters, Dictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Azure.Management.ResourceManager.Fluent.ResourcesOperationsExtensions.UpdateAsync(IResourcesOperations operations, String resourceGroupName, String resourceProviderNamespace, String parentResourcePath, String resourceType, String resourceName, String apiVersion, GenericResourceInner parameters, CancellationToken cancellationToken)

xseeseesee commented 4 years ago

@Rookian Thanks for sharing. I noticed you mentioned the code for creating database:

.WithEdition(DatabaseEdition.Standard)
.WithServiceObjective(ServiceObjectiveName.S0)

Not sure if you have specific reason to use generic resources for updating tags. Can you try with below scenario to update in sql resource itself:

var sqlDatabase = sqlServer.Databases
    .Define(SqlDatabaseName)
    .WithCollation(Collation)
    .WithEdition(DatabaseEdition.Standard)
    .WithServiceObjective(ServiceObjectiveName.S0)
    .WithTag("tag1", "value1")
.Create();

// try below update instead of generic resources
sqlServer.Update()
    .WithTag("tag2", "value2")
    .Apply();

If you have to use generic resources, you might try a different api version manually.

cc: @nickzhums

Rookian commented 4 years ago

I have to use generic resources, because I would like to patch all resources with resource tags.

xseeseesee commented 4 years ago

@Rookian I tried to create a database by the settings you mentioned

.WithEdition(DatabaseEdition.Standard)
.WithServiceObjective(ServiceObjectiveName.S0)

With this, I was able to get the database only with Standard as "currentSku":{"name":"Standard","tier":"Standard","capacity":10} Then I could be able to update the tags successfully.

When I tried to create a database by setting System edition, I got error as you provided 'System' is not a valid database edition in this version of SQL Server. Could you share more details how you created the database with System edition? Thanks.