Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement mgmt resource #47944

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open

Conversation

live1206
Copy link
Member

@live1206 live1206 commented Jan 23, 2025

  • Resolves Implement ResourceProvdier #47744
  • Resolves Generate OperationSource in Azure plugin #48135
  • Adjust to make MPG have similar file structure as DPG.
    • Put CreateRequest methods from RestClientProvider to ClientProvider
    • Implement client methods in Resource directly
    • Omit RestClientProvider
  • Add test to verify the generation
  • Generated MgmtTypeSpec test can build now
  • Leverage existing resource related decorators directly from TypeSpec

@github-actions github-actions bot added Client This issue points to a problem in the data-plane of the library. CodeGen Issues that relate to code generation labels Jan 23, 2025

namespace Azure.Generator.Providers
{
internal class ResourceProvider : TypeProvider
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets call this ResourceClientProvider and see if we can inherit from ClientProvider.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As offline discussed, we would like to reorg the rest clients, operation clients and resource clients. So that, the structure of mgmt resource clients are similar to the DPG clients.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted.

@@ -174,7 +174,7 @@ private ConstructorProvider BuildInitializationConstructor()
skipApiVersionOverrideParameter,
apiVersionOverrideValueParameter,
};
var signature = new ConstructorSignature(Type, $"", MethodSignatureModifiers.Internal, parameters, null);
var signature = new ConstructorSignature(Type, $"", MethodSignatureModifiers.Internal, parameters);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason this class is prefixed with Mgmt? I think Dpg and Mpg LROs work the same other than the type that gets used ArmOperation vs Operation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, it only contains logic for Mgmt, rename it to LongRunningOperationProvider.

private Dictionary<string, OperationSet> _pathToOperationSetMap;
private Dictionary<string, HashSet<OperationSet>> _resourceDataBySpecNameMap;
//TODO: Move these to InputLibrary instead
private Dictionary<RequestPath, OperationSet> _pathToOperationSetMap;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of code in here that is mgmt specific and it starts to get difficult to tell which is which have we considered a mgmt plugin?

Copy link
Member Author

@live1206 live1206 Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely.
Given I am fully working with MPG now, I think it would be better to split them when we have more implementation on DPG, then we can clarify the things in common for both.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#48481 to track the split, it will happen soon after this PR.

@@ -30,6 +31,9 @@ public class AzureClientPlugin : ScmCodeModelPlugin
public override AzureOutputLibrary OutputLibrary => _azureOutputLibrary ??= new();

internal ResourceDetection ResourceDetection { get; } = new();
internal ParentDetection ParentDetection { get; } = new();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't your parent just a property on the client from typespec?

Copy link
Member Author

@live1206 live1206 Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For TypeSpec, we should be able to get the resource hierarchy directly via #48281.
The resource detection logic is mainly for swagger input.
For now, given getArmResources API is not ready for integration. We use it as a temporary workaround.

Eventually, when we have the native way ready, we should get rid of them. And the resource detection should be part of the M4 output conversion to tspCodeModel.json in autorest.csharp.
Added TODO to remove them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As offline discussed, we need to clarify the contract of input types containing the resource hierarchy. And it should be an extensible way of holding it in Azure plugin only.
Then, we can put the temp workaround of resource detection close to the input instead of output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isolated Resource related logic in ResourceBuilder, so we have separated the temp workaround to a central place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will need to have resource related logic in MPG emitter, such as call getArmResources API to get the resource hierarchy. MGC doesn't need to provide any extension for this. MPG can have additional input models to hold them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is only needed for swagger input, have we considered having this in autorest.csharp only?

We can make it part of the codemodel -> input type conversion before we serialize out to the new plugin. That way the new plugin only has 1 way to do things which is to read the information directly from the codemodel.json file and it doesn't care how the data got there.

TryGetApiVersion(ResourceType, out string fooApiVersion);
_fooRestClient = new Foos(Pipeline, Endpoint, fooApiVersion);
#if DEBUG
global::MgmtTypeSpec.FooResource.ValidateResourceId(id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not getting reduced because its in an if debug?

Copy link
Member Author

@live1206 live1206 Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, you were talking about the namespace not reduced.
Yes. I tried to remove the debug block, the namespace was simplified.

Copy link
Member Author

@live1206 live1206 Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raise issue in MGC to fix this
we can replace it with Debug.Assert here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug.Assert can't be used here, it needs a bool condition input, but this method is void.
Use [Conditional("DEBUG")] in the method signature instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

microsoft/typespec#6160 to track the issue in MGC

namespace Samples
{
/// <summary></summary>
public partial class ResponseTypeResource : global::Azure.ResourceManager.ArmResource
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets trim this down to at most 1 method validation at a time. Otherwise this test will be susceptible to any change in generation of any portion of a resource

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to write a method of TypeProvider?
As I can see, there is no public API from TypeProviderWriter. Do we want to expose such API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separated the tests for ResourceClientProvider.
And after exposing AzureOutputLibrary.CreateResourceCore, I realized that we need to simplify the creation of ResourceClientProvider. And it ties to the contract of Input types from MGC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isolated Resource related logic to ResourceBuilder.

public IReadOnlyList<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)> BuildResourceClients()
{
var result = new List<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)>();
var singletonDetection = new SingletonDetection();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a readonly field.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only usage I have now is this place, so that I make it a local variable for now.
We can make it a field if we have other usages later.


private Dictionary<string, (RequestPath ResourcePath, InputClient Client)> EnsureOperationsetMap()
{
var pathToOperationSetMap = CategorizeClients();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok - looks like now we never generate operations by operation groups even in "rest operations" class, I like this a lot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well - no - we still may have the issue that our rest operations are still organized by input clients instead of resources.
But in the future if we could get the extra information about resources from typespec side, this should be resolved automatically therefore this is fine now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say we will have resource across inputClients, we can change the output to Dictionary<string, (RequestPath ResourcePath, IReadOnlyList<InputClient> Clients).
The point is to leverage the existing clients without constructing extra objects.


Response result = await ListAsync(subscriptionId, resourceGroupName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null).ConfigureAwait(false);
return Response.FromValue((FooListResult)result, result);
internal HttpMessage CreateCreateOrUpdateRequest(Guid subscriptionId, string resourceGroupName, string fooName, RequestContent content, RequestContext context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we made a step ahead that in the rest client, we no longer keep the protocol methods and convenience methods, and the resource class would just call the create request methods here.

Do we consider making another step ahead that we could remove this class, and move all these create request methods into the resource class, and put them in another partial class like FooResource.RestClient.cs?
This could save us an object in resource class - now it has to construct this class to create the request.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, I have removed Foos.RestClient.cs and put its CreateRequest methods to FoosRestOperations.cs. I am fine with renaming it to Foos.RestClient.cs. The thing is, we only keep the internal CreateRequest methods.

/// <summary>
/// Create a resource client provider
/// </summary>
/// <param name="inputClient"></param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fill in comments with something meaningful

/// <param name="resourceType"></param>
/// <param name="isSingleton"></param>
/// <returns></returns>
public virtual TypeProvider CreateResourceCore(InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType, bool isSingleton)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is returning a resource client should it be named CreateResourceClientCore?

Does it need to be public or can it be protected?

private FieldProvider _restClientField;
private FieldProvider _resourcetypeField;

public ResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType, bool isSingleton)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't specName part of inputClient? the name from the spec?

I think requestPath and resourceType are redundant or perhaps resourceType is redundant with the resourceData?


namespace Azure.Generator
{
internal class ResourceBuilder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a resource builder and how does it fit into the new design of Providers?

@@ -27,7 +27,7 @@ internal class ResourceVisitor : ScmLibraryVisitor

private static void TransformResource(TypeProvider type)
{
if (type is ModelProvider && AzureClientPlugin.Instance.OutputLibrary.IsResource(type.Name))
if (type is ModelProvider && AzureClientPlugin.Instance.ResourceBuilder.IsResource(type.Name))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we type check here if its a ResourceDataProvider which inherits from ModelProvider?


namespace Azure.Generator.Utilities
{
internal class SingletonDetection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure these Detection classes fit into the new design. It feels like we are pulling in autorest.csharp concepts.

_client = client;
}

global::Samples.ResponseTypeResource global::Azure.Core.IOperationSource<global::Samples.ResponseTypeResource>.CreateResult(global::Azure.Response response, global::System.Threading.CancellationToken cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend trimming down these test files to single method only to minimize test churn when we tweak internal implementations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Client This issue points to a problem in the data-plane of the library. CodeGen Issues that relate to code generation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Generate OperationSource in Azure plugin Implement ResourceProvdier
4 participants