diff --git a/end-to-end-solutions/Luna/Resources/Builds/2.0/apiApp.zip b/end-to-end-solutions/Luna/Resources/Builds/2.0/apiApp.zip index fbd4e42..6602f14 100644 Binary files a/end-to-end-solutions/Luna/Resources/Builds/2.0/apiApp.zip and b/end-to-end-solutions/Luna/Resources/Builds/2.0/apiApp.zip differ diff --git a/end-to-end-solutions/Luna/Resources/Builds/2.0/isvApp.zip b/end-to-end-solutions/Luna/Resources/Builds/2.0/isvApp.zip index e5b587e..17a6ed8 100644 Binary files a/end-to-end-solutions/Luna/Resources/Builds/2.0/isvApp.zip and b/end-to-end-solutions/Luna/Resources/Builds/2.0/isvApp.zip differ diff --git a/end-to-end-solutions/Luna/Resources/Deployment/CheckPermissions.ps1 b/end-to-end-solutions/Luna/Resources/Deployment/CheckPermissions.ps1 new file mode 100644 index 0000000..796ccb8 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Deployment/CheckPermissions.ps1 @@ -0,0 +1,38 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. + +param ( + [Parameter(Mandatory=$true)] + [string]$tenantId = "default", + + [Parameter(Mandatory=$true)] + [string]$subscriptionId = "default", + + [Parameter(Mandatory=$true)] + [string]$userId = "default", + + [Parameter(Mandatory=$true)] + [string]$location = "default" +) + +$rpList = @('Microsoft.Network','Microsoft.Compute','Microsoft.ContainerInstance','Microsoft.ContainerService','Microsoft.Insights','Microsoft.Sql','Microsoft.MachineLearningServices','Microsoft.Storage','Microsoft.ApiManagement','Microsoft.KeyVault','Microsoft.Web','Microsoft.OperationalInsights') + +Connect-AzAccount -Tenant $tenantId + +Set-AzContext -Subscription $subscriptionId + +Write-Host "Enabling required Resource Providers and check the region availability" + +$rpList | ForEach-Object -Process { + $rp = Register-AzResourceProvider -ProviderNamespace $_; + If (-not $rp.Locations.Contains($location)){Write-Error "Resource Provider" $_ "is not enabled in region " $location}} + +Write-Host "Check if you are owner or contributor of the Azure subscription" + +$scope = "/subscriptions/"+$subscriptionId +$assignment = Get-AzRoleAssignment -Scope $scope | Where-Object {$_.Scope -eq $scope -and $_.SignInName -eq $userId -and ($_.RoleDefinitionName -eq "Owner" -or $_.RoleDefinitionName -eq "Contributor")} + +If ($assignment.Length -le 0){ + Write-Error "You are neither owner nor contributor of Azure subscription " $subscriptionId +} + diff --git a/end-to-end-solutions/Luna/Resources/Deployment/Deploy.ps1 b/end-to-end-solutions/Luna/Resources/Deployment/Deploy.ps1 index 92a9791..74b6405 100644 --- a/end-to-end-solutions/Luna/Resources/Deployment/Deploy.ps1 +++ b/end-to-end-solutions/Luna/Resources/Deployment/Deploy.ps1 @@ -1,3 +1,6 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. + param ( [Parameter(Mandatory=$true)] [string]$uniqueName = "default", @@ -35,6 +38,16 @@ [string]$apiWebAppInsightsName = "default", + [string]$apimName = "default", + + [string]$apimTier = "Developer", + + [string]$apimCapacity = 1, + + [string]$amlWorkspaceName = "default", + + [string]$amlWorkspaceSku = "Basic", + [string]$azureMarketplaceAADApplicationName = "default", [string]$azureMarketplaceAADApplicationId = "00000000-0000-0000-0000-000000000000", @@ -130,7 +143,7 @@ function Create-AzureADApplication{ function GrantKeyVaultAccessToWebApp{ param($resourceGroupName, $keyVaultName, $webAppName) $webapp = Get-AzWebApp -ResourceGroupName $resourceGroupname -Name $webAppName - Set-AzKeyVaultAccessPolicy -VaultName $keyVaultName -ObjectId $webapp.Identity.PrincipalId -PermissionsToSecrets list,get + Set-AzKeyVaultAccessPolicy -VaultName $keyVaultName -ObjectId $webapp.Identity.PrincipalId -PermissionsToSecrets list,get,set,delete } function Get-PublishingProfileCredentials($resourceGroupName, $webAppName){ @@ -240,6 +253,8 @@ $enduserWebAppName = GetNameForAzureResources -defaultName $enduserWebAppName -r $apiWebAppName = GetNameForAzureResources -defaultName $apiWebAppName -resourceTypeSuffix "-apiapp" -uniqueName $uniqueName $apiWebJobName = GetNameForAzureResources -defaultName $apiWebJobName -resourceTypeSuffix "-apiwebjob" -uniqueName $uniqueName $apiWebAppInsightsName = GetNameForAzureResources -defaultName $apiWebAppInsightsName -resourceTypeSuffix "-apiappinsights" -uniqueName $uniqueName +$apimName = GetNameForAzureResources -defaultName $apimName -resourceTypeSuffix "-apim" -uniqueName $uniqueName +$amlWorkspaceName = GetNameForAzureResources -defaultName $amlWorkspaceName -resourceTypeSuffix "-aml" -uniqueName $uniqueName $azureMarketplaceAADApplicationName = GetNameForAzureResources -defaultName $azureMarketplaceAADApplicationName -resourceTypeSuffix "-azuremarketplace-aad" -uniqueName $uniqueName $azureResourceManagerAADApplicationName = GetNameForAzureResources -defaultName $azureResourceManagerAADApplicationName -resourceTypeSuffix "-azureresourcemanager-aad" -uniqueName $uniqueName @@ -271,7 +286,7 @@ if ($userApplicationSubscriptionId -eq "default"){ $userApplicationSubscriptionId = $currentContext.Subscription.Id } -$currentUser = Get-AzADUser -Mail $accountId +$currentUser = Get-AzADUser -UserPrincipalName $accountId if ($adminAccounts -eq "default"){ @@ -284,6 +299,8 @@ Write-Host "Create resource group" $resourceGroupName New-AzResourceGroup -Name $resourceGroupName -Location $location Write-Host "Deploy ARM template in resource group" $resourceGroupName +$deployAPIM = $enableV2 -eq 'true' +$deployAML = $enableV2 -eq 'true' New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName ` -TemplateFile .\main.json ` -keyVaultName $keyVaultName ` @@ -300,7 +317,16 @@ New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName ` -sqlAdministratorUsername $sqlServerAdminUsername ` -tenantId $tenantId ` -objectId $objectId ` - -buildLocation $buildLocation + -buildLocation $buildLocation ` + -apimAdminEmail $accountId ` + -orgName $companyName ` + -apimName $apimName ` + -apimTier $apimTier ` + -apimCapacity $apimCapacity ` + -deployAPIM $deployAPIM ` + -workspaceName $amlWorkspaceName ` + -workspaceSku $amlWorkspaceSku ` + -deployAML $deployAML $filter = "AppId eq '"+$webAppAADApplicationId+"'" @@ -375,6 +401,10 @@ if ($isNewApp){ Write-Host "Assign subscription contribution role to the service principal." $scope = '/subscriptions/'+$userApplicationSubscriptionId NewAzureRoleAssignment -objectId $principalId -scope $scope -retryCount 10 + + Write-Host "Assign contribution role on the AML workspace to the service principal." + $scope = '/subscriptions/'+$userApplicationSubscriptionId+'/resourceGroups/'+$resourceGroupName+'/providers/Microsoft.MachineLearningServices/workspaces/'+$amlWorkspaceName + NewAzureRoleAssignment -objectId $principalId -scope $scope -retryCount 10 } #grant key vault access to API app @@ -421,6 +451,25 @@ $connectionString = "Server=tcp:" + $sqlServerInstanceName + ",1433;Initial Cata $secretvalue = ConvertTo-SecureString $connectionString -AsPlainText -Force Set-AzKeyVaultSecret -VaultName $keyVaultName -Name 'connection-string' -SecretValue $secretvalue +$apimTenantAccessId = 'integration' +$controllerBaseUrl = '' + +if ($enableV2 -eq 'true'){ + Write-Host "Get APIM management key" + $apimContext = New-AzApiManagementContext -ResourceGroupName $resourceGroupName -ServiceName $apimName + Set-AzApiManagementTenantAccess -Context $apimContext -Enabled $True + + $tenantAccess = Get-AzApiManagementTenantAccess -Context $apimContext + $tenantAccessSecret = Get-AzApiManagementTenantAccessSecret -Context $apimContext + $apimPrimaryKey = $tenantAccessSecret.PrimaryKey + $secretvalue = ConvertTo-SecureString $apimPrimaryKey -AsPlainText -Force + Set-AzKeyVaultSecret -VaultName $keyVaultName -Name 'apim-key' -SecretValue $secretvalue + + $apimTenantAccessId = $tenantAccess.Id + $controllerBaseUrl = "https://"+ $apiWebAppName +".azurewebsites.net" +} + + Write-Host "Update app settings" $appsettings = @{} $appsettings["SecuredCredentials:VaultName"] = $keyVaultName; @@ -438,6 +487,15 @@ $appsettings["AzureAD:TenantId"] = $tenantId; $appsettings["ISVPortal:AdminAccounts"] = $adminAccounts; $appsettings["ISVPortal:AdminTenant"] = $adminTenantId; +$appsettings["SecuredCredentials:APIM:Config:VaultName"] = $keyVaultName; +$appsettings["SecuredCredentials:APIM:Config:SubscriptionId"] = $lunaServiceSubscriptionId; +$appsettings["SecuredCredentials:APIM:Config:ResourceGroupName"] = $resourceGroupName; +$appsettings["SecuredCredentials:APIM:Config:APIMServiceName"] = $apimName; +$appsettings["SecuredCredentials:APIM:Config:APIVersion"] = '2019-12-01'; +$appsettings["SecuredCredentials:APIM:Config:UId"] = $apimTenantAccessId; +$appsettings["SecuredCredentials:APIM:Config:Key"] = 'apim-key'; +$appsettings["SecuredCredentials:APIM:Config:ControllerBaseUrl"] = $controllerBaseUrl; + $appInsightsApp = Get-AzApplicationInsights -ResourceGroupName $resourceGroupName -name $apiWebAppInsightsName $appsettings["ApplicationInsights:InstrumentationKey"] = $appInsightsApp.InstrumentationKey; $appsettings["WebJob:APIServiceUrl"] = "https://" + $apiWebAppName + ".azurewebsites.net/api"; diff --git a/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/latest/db_provisioning.sql b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/latest/db_provisioning.sql index ee1057a..98eeab0 100644 --- a/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/latest/db_provisioning.sql +++ b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/latest/db_provisioning.sql @@ -1,3 +1,6 @@ +-- Copyright (c) Microsoft Corporation. +-- Licensed under the MIT license. + SET ANSI_NULLS ON GO @@ -47,9 +50,9 @@ DROP TABLE [dbo].[AadSecretTmps] END GO -IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'SubscriptionCustomMeterUsage' AND sch.name = 'dbo') +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'SubscriptionCustomMeterUsages' AND sch.name = 'dbo') BEGIN -DROP TABLE [dbo].[SubscriptionCustomMeterUsage] +DROP TABLE [dbo].[SubscriptionCustomMeterUsages] END GO @@ -149,6 +152,36 @@ DROP TABLE [dbo].[Offers] END GO +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'APISubscriptions' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[APISubscriptions] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'APIVersions' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[APIVersions] +END +GO + + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Deployments' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Deployments] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Products' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Products] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'AMLWorkspaces' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[AMLWorkspaces] +END +GO CREATE TABLE [dbo].[Offers]( [Id] [bigint] IDENTITY(1,1) NOT NULL, @@ -458,8 +491,106 @@ CREATE TABLE [dbo].[WebhookWebhookParameters]( CONSTRAINT FK_WebhookParameterId_WebhookWebhookParameters FOREIGN KEY (WebhookParameterId) REFERENCES WebhookParameters(Id) ) +CREATE TABLE [dbo].[AMLWorkspaces]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [WorkspaceName] [nvarchar](50) NOT NULL, + [Region] [nvarchar](64) NOT NULL, + [ResourceId] [nvarchar](max) NOT NULL, + [AADApplicationId] [uniqueidentifier] NOT NULL, + [AADTenantId] [uniqueidentifier] NULL, -- allow null for backward compatibility + [AADApplicationSecrets] [nvarchar](128) NOT NULL, + PRIMARY KEY (Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[Products]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [ProductName] [nvarchar](50) NOT NULL, + [ProductType] [nvarchar](64) NOT NULL, + [HostType] [nvarchar](64) NOT NULL, + [Owner] [nvarchar](512) NOT NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY (Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[Deployments]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [ProductId] [bigint] NOT NULL, + [DeploymentName] [nvarchar](50) NOT NULL, + [Description] [nvarchar](1024) NOT NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_productId_Deployments FOREIGN KEY (ProductId) REFERENCES Products(Id) +) ON [PRIMARY] +GO +CREATE TABLE [dbo].[APIVersions]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [DeploymentId] [bigint] NOT NULL, + [VersionName] [nvarchar](50) NOT NULL, + [RealTimePredictAPI] [nvarchar](max) NULL, + [TrainModelAPI] [nvarchar](max) NULL, + [BatchInferenceAPI] [nvarchar](max) NULL, + [DeployModelAPI] [nvarchar](max) NULL, + [AuthenticationType] [nvarchar](8) NOT NULL, + [AuthenticationKey] [nvarchar](64) NULL, + [AMLWorkspaceId] [bigint] NULL, + [AdvancedSettings] [nvarchar](max) NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_deploymentId_APIVersions FOREIGN KEY (DeploymentId) REFERENCES Deployments(Id), + -- CONSTRAINT FK_amlworkspaceId_APIVersions FOREIGN KEY (AMLWorkspaceId) REFERENCES AMLWorkspaces(Id) +) ON [PRIMARY] +GO +CREATE TABLE [dbo].[APISubscriptions]( + [SubscriptionId] [uniqueidentifier] NOT NULL, + [DeploymentId] [bigint] NOT NULL, + [SubscriptionName] [nvarchar](64) NOT NULL, + [userId] [nvarchar](512) NOT NULL, + [Status] [nvarchar](32) NULL, + [BaseUrl] [nvarchar](max) NULL, + [PrimaryKey] [nvarchar](64) NULL, + [SecondaryKey] [nvarchar](64) NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY (SubscriptionId), + CONSTRAINT FK_deploymentId_APISubscriptions FOREIGN KEY (DeploymentId) REFERENCES Deployments(Id) +) ON [PRIMARY] +GO +CREATE TABLE [dbo].[APISubscriptions_tmp]( + [Id] [bigint] identity(1,1) NOT NULL, + [SubscriptionId] [uniqueidentifier] NOT NULL, + [DeploymentId] [bigint] NOT NULL, + [SubscriptionName] [nvarchar](64) NOT NULL, + [userId] [nvarchar](512) NOT NULL, + [Status] [nvarchar](32) NULL, + [BaseUrl] [nvarchar](max) NULL, + [PrimaryKey] [nvarchar](64) NULL, + [SecondaryKey] [nvarchar](64) NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY CLUSTERED([SubscriptionId] ASC)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +GO +insert into APISubscriptions_tmp select * from APISubscriptions +GO + +drop table apisubscriptions +GO + +EXEC sp_rename 'apisubscriptions_tmp', 'APISubscriptions' +GO + +ALTER TABLE [dbo].[APISubscriptions] WITH CHECK ADD CONSTRAINT [FK_deploymentId_APISubscriptions] FOREIGN KEY([DeploymentId]) +REFERENCES [dbo].[Deployments] ([Id]) +GO +ALTER TABLE [dbo].[APISubscriptions] CHECK CONSTRAINT [FK_deploymentId_APISubscriptions] +GO \ No newline at end of file diff --git a/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.0/db_provisioning.sql b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.0/db_provisioning.sql index 642831d..6978d53 100644 --- a/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.0/db_provisioning.sql +++ b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.0/db_provisioning.sql @@ -1,3 +1,6 @@ +-- Copyright (c) Microsoft Corporation. +-- Licensed under the MIT license. + SET ANSI_NULLS ON GO diff --git a/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.1/db_provisioning.sql b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.1/db_provisioning.sql index ee1057a..ec46ff8 100644 --- a/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.1/db_provisioning.sql +++ b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v1.1/db_provisioning.sql @@ -1,3 +1,6 @@ +-- Copyright (c) Microsoft Corporation. +-- Licensed under the MIT license. + SET ANSI_NULLS ON GO diff --git a/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v2.0/db_provisioning.sql b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v2.0/db_provisioning.sql new file mode 100644 index 0000000..f54b563 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Deployment/SqlScripts/v2.0/db_provisioning.sql @@ -0,0 +1,573 @@ +-- Copyright (c) Microsoft Corporation. +-- Licensed under the MIT license. + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +Declare @username nvarchar(128) +Declare @password nvarchar(128) +Declare @sqlstmt nvarchar(512) + +SET @password = $(password) +SET @username = $(username) +print @username +print @password + +IF NOT EXISTS (SELECT * FROM sys.sysusers WHERE name = @username) +BEGIN + Set @sqlstmt ='CREATE USER '+@username +' WITH PASSWORD ='''+@password +'''' + Exec (@sqlstmt) +END + +EXEC sp_addrolemember N'db_owner', @username +GO + + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'WebhookWebhookParameters' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[WebhookWebhookParameters] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'WebhookParameters' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[WebhookParameters] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'AadSecrets' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[AadSecrets] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'AadSecretTmps' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[AadSecretTmps] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'SubscriptionCustomMeterUsages' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[SubscriptionCustomMeterUsages] +END +GO + + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'IpAddresses' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[IpAddresses] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'IpBlocks' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[IpBlocks] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'IpConfigs' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[IpConfigs] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'OfferParameters' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[OfferParameters] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'RestrictedUsers' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[RestrictedUsers] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'ArmTemplateArmTemplateParameters' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[ArmTemplateArmTemplateParameters] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'ArmTemplateParameters' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[ArmTemplateParameters] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'SubscriptionParameters' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[SubscriptionParameters] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Subscriptions' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Subscriptions] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'CustomMeterDimensions' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[CustomMeterDimensions] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Plans' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Plans] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'CustomMeters' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[CustomMeters] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'TelemetryDataConnectors' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[TelemetryDataConnectors] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Webhooks' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Webhooks] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'ArmTemplates' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[ArmTemplates] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Offers' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Offers] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'APISubscriptions' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[APISubscriptions] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'APIVersions' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[APIVersions] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Deployments' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Deployments] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'Products' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[Products] +END +GO + +IF EXISTS (select * from sys.tables tb join sys.schemas sch on tb.schema_id = sch.schema_id where tb.name = 'AMLWorkspaces' AND sch.name = 'dbo') +BEGIN +DROP TABLE [dbo].[AMLWorkspaces] +END +GO + + +CREATE TABLE [dbo].[Offers]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [OfferName] [nvarchar](50) NOT NULL, + [OfferAlias] [nvarchar](128) NOT NULL, + [OfferVersion] [nvarchar](50) NOT NULL, + [Owners] [nvarchar](512) NOT NULL, + [HostSubscription] uniqueidentifier NOT NULL, + [Status] [nvarchar](16) NOT NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + [DeletedTime] [datetime2](7), + [ContainerName] [uniqueidentifier] NOT NULL, + [ManualActivation] [bit], + [ManualCompleteOperation] [bit], + PRIMARY KEY (Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[ArmTemplates]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [OfferId] [bigint] NOT NULL, + [TemplateName] [nvarchar](128) NOT NULL, + [TemplateFilePath] [nvarchar](1024) NOT NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_offerId_armTemplates FOREIGN KEY (OfferId) REFERENCES offers(Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[Webhooks]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [OfferId] [bigint] NOT NULL, + [WebhookName] [nvarchar](128) NOT NULL, + [WebhookUrl] [nvarchar](1024) NOT NULL, +PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] +GO + +ALTER TABLE [dbo].[Webhooks] WITH CHECK ADD CONSTRAINT [FK_offerId_webhook] FOREIGN KEY([OfferId]) +REFERENCES [dbo].[Offers] ([Id]) +GO + +ALTER TABLE [dbo].[Webhooks] CHECK CONSTRAINT [FK_offerId_webhook] +GO + +CREATE TABLE [dbo].[Plans]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [OfferId] [bigint] NOT NULL, + [PlanName] [nvarchar](50) NOT NULL, + [DataRetentionInDays] [int] NOT NULL, + [SubscribeArmTemplateId] bigint NULL, + [UnsubscribeArmTemplateId] bigint NULL, + [SuspendArmTemplateId] bigint NULL, + [DeleteDataArmTemplateId] bigint NULL, + [SubscribeWebhookId] bigint NULL, + [UnsubscribeWebhookId] bigint NULL, + [SuspendWebhookId] bigint NULL, + [DeleteDataWebhookId] bigint NULL, + [PriceModel] [nvarchar](16) NOT NULL, + [MonthlyBase] [float] NULL, + [AnnualBase] [float] NULL, + [PrivatePlan] [bit] NOT NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_offer_id_plans FOREIGN KEY (OfferId) REFERENCES offers(Id), + CONSTRAINT FK_subscribeArmTemplateId_plans FOREIGN KEY (SubscribeArmTemplateId) REFERENCES ArmTemplates(Id), + CONSTRAINT FK_unsubscribeArmTemplateId_plans FOREIGN KEY (UnsubscribeArmTemplateId) REFERENCES ArmTemplates(Id), + CONSTRAINT FK_suspendArmTemplateId_plans FOREIGN KEY (SuspendArmTemplateId) REFERENCES ArmTemplates(Id), + CONSTRAINT FK_deleteDataArmTemplateId_plans FOREIGN KEY (DeleteDataArmTemplateId) REFERENCES ArmTemplates(Id), + CONSTRAINT FK_subscribeWebhookId_plans FOREIGN KEY (SubscribeWebhookId) REFERENCES Webhooks(Id), + CONSTRAINT FK_unsubscribeWebhookId_plans FOREIGN KEY (UnsubscribeWebhookId) REFERENCES Webhooks(Id), + CONSTRAINT FK_suspendWebhookId_plans FOREIGN KEY (SuspendWebhookId) REFERENCES Webhooks(Id), + CONSTRAINT FK_deleteDataWebhookId_plans FOREIGN KEY (DeleteDataWebhookId) REFERENCES Webhooks(Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[OfferParameters]( + [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, + [OfferId] [bigint] NOT NULL, + [ParameterName] [nvarchar](128) NOT NULL, + [DisplayName] [nvarchar](128) NOT NULL, + [Description] [nvarchar](max) NOT NULL, + [ValueType] [nvarchar](16) NOT NULL, + [FromList] bit NOT NULL, + [ValueList] [nvarchar](max), + [Maximum] bigint, + [Minimum] bigint, + CONSTRAINT FK_offer_id_offer_parameters FOREIGN KEY (OfferId) + REFERENCES Offers(Id) +) + +CREATE TABLE [dbo].[Subscriptions]( + [SubscriptionId] [uniqueidentifier] NOT NULL, + [Name] [nvarchar](128) NOT NULL, + [PublisherId] [nvarchar](50) NULL, + [OfferId] bigint NOT NULL, + [PlanId] bigint NOT NULL, + [Quantity] [int] NOT NULL, + [BeneficiaryTenantId] [uniqueidentifier] NULL, + [PurchaserTenantId] [uniqueidentifier] NULL, + [Status] [nvarchar](128) NOT NULL, + [IsTest] [bit] NULL, + [AllowedCustomerOperationsMask] [int] NULL, + [SessionMode] [nvarchar](128) NULL, + [SandboxType] [nvarchar](128) NULL, + [IsFreeTrial] [bit] NULL, + [CreatedTime] [datetime2](7) NULL, + [ActivatedTime] [datetime2](7) NULL, + [LastUpdatedTime] [datetime2](7) NULL, + [LastSuspendedTime] [datetime2](7) NULL, + [UnsubscribedTime] [datetime2](7) NULL, + [DataDeletedTime] [datetime2](7) NULL, + [OperationId] [uniqueidentifier] NULL, + [DeploymentName] [nvarchar](128) NULL, + [DeploymentId] [uniqueidentifier] NULL, + [ResourceGroup] [nvarchar](128) NULL, + [Owner] [nvarchar](128) NULL, + [ActivatedBy] [nvarchar](128) NULL, + [LastException] [nvarchar](max) NULL, + [ProvisioningStatus] [nvarchar](64) NULL, + [ProvisioningType] [nvarchar](64) NULL, + [RetryCount] int NULL, + [EntryPointUrl] [nvarchar](1024) NULL, + CONSTRAINT FK_offer_id_subscriptions FOREIGN KEY (OfferId) REFERENCES Offers(Id), + CONSTRAINT FK_plan_id_subscriptions FOREIGN KEY (PlanId) REFERENCES Plans(Id), + PRIMARY KEY CLUSTERED ( + [SubscriptionId] ASC + ) + WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[SubscriptionParameters]( + [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, + [SubscriptionId] uniqueidentifier NOT NULL, + [Name] [nvarchar](128) NOT NULL, + [Type] [nvarchar](16) NOT NULL, + [Value] [nvarchar](max) NOT NULL, + CONSTRAINT FK_subscription_id_subscription_parameters FOREIGN KEY (SubscriptionId) + REFERENCES Subscriptions(SubscriptionId) +) +GO + +CREATE TABLE [dbo].[IpConfigs]( + [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, + [Name] [nvarchar](50) NOT NULL, + [IPsPerSub] int NOT NULL, + [OfferId] bigint NOT NULL, + CONSTRAINT FK_OfferId_IpConfigs FOREIGN KEY (OfferId) + REFERENCES Offers(Id) +) + +CREATE TABLE [dbo].[IpBlocks]( + [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, + [CIDR] [nvarchar](32) NOT NULL, + [IpConfigId] bigint NOT NULL, + CONSTRAINT FK_IpConfigId_IpBlocks FOREIGN KEY (IpConfigId) + REFERENCES IpConfigs(Id) +) + +CREATE TABLE [dbo].[IpAddresses]( + [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, + [Value] [nvarchar](32) NOT NULL, + [IsAvailable] bit NOT NULL, + [IpBlockId] bigint NOT NULL, + [SubscriptionId] [uniqueidentifier], + CONSTRAINT FK_IpBlockId_IpAddresses FOREIGN KEY (IpBlockId) REFERENCES IpBlocks(id), + CONSTRAINT FK_SubscriptionId_IpAddresses FOREIGN KEY (SubscriptionId) REFERENCES Subscriptions(SubscriptionId) +) + +CREATE TABLE [dbo].[ArmTemplateParameters]( + [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, + [OfferId] BIGINT NOT NULL, + [Name] [nvarchar](128) NOT NULL, + [Type] [nvarchar](16) NOT NULL, + [Value] [nvarchar](max) NOT NULL, + CONSTRAINT FK_offer_id_arm_templates_parameters FOREIGN KEY (OfferId) + REFERENCES Offers(Id) +) + +CREATE TABLE [dbo].[ArmTemplateArmTemplateParameters]( + [ArmTemplateId] [bigint] NOT NULL, + [ArmTemplateParameterId] [bigint] NOT NULL, + PRIMARY KEY (ArmTemplateId, ArmTemplateParameterId), + CONSTRAINT FK_ArmTemplateId_ArmTemplateArmTemplateParameters FOREIGN KEY (ArmTemplateId) REFERENCES ArmTemplates(Id), + CONSTRAINT FK_ArmTemplateParameterId_ArmTemplateArmTemplateParameters FOREIGN KEY (ArmTemplateParameterId) REFERENCES ArmTemplateParameters(Id) +) + +CREATE TABLE [dbo].[RestrictedUsers]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [PlanId] bigint NOT NULL, + [TenantId] [uniqueidentifier] NOT NULL, + [Description] [nchar](50) NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_plan_id_restricted_users FOREIGN KEY (PlanId) + REFERENCES Plans(Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[TelemetryDataConnectors]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [Name] [nvarchar](64) NOT NULL, + [Type] [nvarchar](512) NOT NULL, + [Configuration] [nvarchar](max) NOT NULL, + PRIMARY KEY (Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[CustomMeters]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [OfferId] [bigint] NOT NULL, + [MeterName] [nvarchar](50) NOT NULL, + [TelemetryDataConnectorId] [bigint] NOT NULL, + [TelemetryQuery] [nvarchar](max) NOT NULL, + PRIMARY KEY (id), + CONSTRAINT FK_telemetry_data_connector_id_custom_meters FOREIGN KEY (TelemetryDataConnectorId) + REFERENCES TelemetryDataConnectors(Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[CustomMeterDimensions]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [MeterId] bigint NOT NULL, + [PlanId] bigint NOT NULL, + [MonthlyUnlimited] [bit] NULL, + [AnnualUnlimited] [bit] NULL, + [MonthlyQuantityIncludedInBase] [int] NULL, + [AnnualQuantityIncludedInBase] [int] NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_meter_id_custom_meter_dimensions FOREIGN KEY (MeterId) + REFERENCES CustomMeters(Id), + CONSTRAINT FK_plan_id_custom_meter_dimensions FOREIGN KEY (PlanId) + REFERENCES Plans(Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[SubscriptionCustomMeterUsages]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [MeterId] bigint NOT NULL, + [SubscriptionId] uniqueidentifier NOT NULL, + [CreatedTime] [datetime2] NOT NULL, + [LastUpdatedTime] [datetime2], + [LastErrorReportedTime] [datetime2], + [LastError] [nvarchar](max), + [IsEnabled] [bit], + [UnsubscribedTime] [datetime2], + [EnabledTime] [datetime2], + [DisabledTime] [datetime2], + PRIMARY KEY (Id), + CONSTRAINT FK_meter_id_subscription_custom_meter_usage FOREIGN KEY (MeterId) + REFERENCES CustomMeters(Id), + CONSTRAINT FK_subscription_id_subscription_custom_meter_usage FOREIGN KEY (SubscriptionId) + REFERENCES Subscriptions(SubscriptionId) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[AadSecretTmps]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [OfferId] bigint NOT NULL, + [Name] [nvarchar](64) NOT NULL, + [TenantId] [uniqueidentifier] NOT NULL, + [ApplicationId] [uniqueidentifier] NOT NULL, + [ClientSecret] [nvarchar](64), + PRIMARY KEY (Id), + CONSTRAINT FK_offer_id_aad_secret_tmps FOREIGN KEY (OfferId) + REFERENCES Offers(Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[AadSecrets]( + [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY, + [SecretType] [nvarchar](64) NOT NULL, + [SecretName] [nvarchar](128) NOT NULL, + [KeyVaultName] [nvarchar](128) NOT NULL, + [OfferId] BIGINT NOT NULL, + CONSTRAINT FK_offer_id_aad_secrets FOREIGN KEY (OfferId) + REFERENCES Offers(Id) +) ON [PRIMARY] +GO + +CREATE TABLE [dbo].[WebhookParameters]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [OfferId] [bigint] NOT NULL, + [Name] [nvarchar](128) NOT NULL, + [Value] [nvarchar](max) NOT NULL, +PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +GO + +ALTER TABLE [dbo].[WebhookParameters] WITH CHECK ADD CONSTRAINT [FK_offer_id_webhook_parameters] FOREIGN KEY([OfferId]) +REFERENCES [dbo].[Offers] ([Id]) +GO + +ALTER TABLE [dbo].[WebhookParameters] CHECK CONSTRAINT [FK_offer_id_webhook_parameters] +GO + +CREATE TABLE [dbo].[WebhookWebhookParameters]( + [WebhookId] [bigint] NOT NULL, + [WebhookParameterId] [bigint] NOT NULL, + PRIMARY KEY (WebhookId, WebhookParameterId), + CONSTRAINT FK_WebhookId_WebhookWebhookParameters FOREIGN KEY (WebhookId) REFERENCES Webhooks(Id), + CONSTRAINT FK_WebhookParameterId_WebhookWebhookParameters FOREIGN KEY (WebhookParameterId) REFERENCES WebhookParameters(Id) +) +GO + +CREATE TABLE [dbo].[AMLWorkspaces]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [WorkspaceName] [nvarchar](50) NOT NULL, + [ResourceId] [nvarchar](max) NOT NULL, + [AADApplicationId] [uniqueidentifier] NOT NULL, + [AADTenantId] [uniqueidentifier] NULL, + [AADApplicationSecretName] [nvarchar](128) NOT NULL, + [Region] [nvarchar](32) NOT NULL, + PRIMARY KEY (Id) +) +GO + +CREATE TABLE [dbo].[Products]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [ProductName] [nvarchar](50) NOT NULL, + [ProductType] [nvarchar](64) NOT NULL, + [HostType] [nvarchar](64) NOT NULL, + [Owner] [nvarchar](512) NOT NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY (Id) +) +GO + +CREATE TABLE [dbo].[Deployments]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [ProductId] [bigint] NOT NULL, + [DeploymentName] [nvarchar](50) NOT NULL, + [Description] [nvarchar](1024) NOT NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_ProductId_Deployments FOREIGN KEY (ProductId) REFERENCES Products(Id) +) +GO + +CREATE TABLE [dbo].[APIVersions]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [DeploymentId] [bigint] NOT NULL, + [VersionName] [nvarchar](50) NOT NULL, + [RealTimePredictAPI] [nvarchar](max) NULL, + [TrainModelAPI] [nvarchar](max) NULL, + [BatchInferenceAPI] [nvarchar](max) NULL, + [DeployModelAPI] [nvarchar](max) NULL, + [AuthenticationType] [nvarchar](8) NOT NULL, + [AuthenticationKeySecretName] [nvarchar](256) NULL, + [AMLWorkspaceId] [bigint] NULL, + [AdvancedSettings] [nvarchar](max) NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + [VersionSourceType] [nvarchar](64) NULL, + [GitUrl] [nvarchar](max) NULL, + [GitPersonalAccessTokenSecretName] [nvarchar](256) NULL, + [ProjectFileUrl] [nvarchar](max) NULL, + [GitVersion] [nvarchar](max) NULL, + PRIMARY KEY (Id), + CONSTRAINT FK_DeploymentId_APIVersions FOREIGN KEY (DeploymentId) REFERENCES Deployments(Id) +) +GO + +CREATE TABLE [dbo].[APISubscriptions]( + [Id] [bigint] IDENTITY(1,1) NOT NULL, + [SubscriptionId] [uniqueidentifier] NOT NULL, + [DeploymentId] [bigint] NOT NULL, + [SubscriptionName] [nvarchar](64) NOT NULL, + [userId] [nvarchar](512) NOT NULL, + [Status] [nvarchar](32) NULL, + [BaseUrl] [nvarchar](max) NULL, + [PrimaryKey] [nvarchar](64) NULL, + [SecondaryKey] [nvarchar](64) NULL, + [CreatedTime] [datetime2](7) NOT NULL, + [LastUpdatedTime] [datetime2](7) NOT NULL, + PRIMARY KEY (SubscriptionId), + CONSTRAINT FK_DeploymentId_APISubscriptions FOREIGN KEY (DeploymentId) REFERENCES Deployments(Id) +) +GO + + diff --git a/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/SqlScripts/db_upgrade.sql b/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/SqlScripts/db_upgrade.sql index c97c550..baa2986 100644 --- a/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/SqlScripts/db_upgrade.sql +++ b/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/SqlScripts/db_upgrade.sql @@ -1,3 +1,6 @@ +-- Copyright (c) Microsoft Corporation. +-- Licensed under the MIT license. + DECLARE @current_version bigint DECLARE @target_version bigint DECLARE @upgrade_version bigint diff --git a/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/Upgrade.ps1 b/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/Upgrade.ps1 index 7dd680f..62412d8 100644 --- a/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/Upgrade.ps1 +++ b/end-to-end-solutions/Luna/Resources/Deployment/UpgradeScripts/Upgrade.ps1 @@ -1,3 +1,6 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. + param ( [Parameter(Mandatory=$true)] [string]$uniqueName = "default", diff --git a/end-to-end-solutions/Luna/Resources/Deployment/main.json b/end-to-end-solutions/Luna/Resources/Deployment/main.json index 2f5803d..787fab3 100644 --- a/end-to-end-solutions/Luna/Resources/Deployment/main.json +++ b/end-to-end-solutions/Luna/Resources/Deployment/main.json @@ -94,6 +94,60 @@ "metadata": { "description": "The object id for key vault access policy." } + }, + "apimAdminEmail": { + "type": "string", + "metadata": { + "description": "The APIM admin email." + } + }, + "orgName": { + "type": "string", + "metadata": { + "description": "The orgnization name." + } + }, + "apimName": { + "type": "string", + "metadata": { + "description": "APIM service name." + } + }, + "apimTier": { + "type": "string", + "metadata": { + "description": "APIM pricing tier." + } + }, + "apimCapacity": { + "type": "int", + "metadata": { + "description": "APIM capacity." + } + }, + "deployAPIM": { + "type": "bool", + "metadata": { + "description": "If enable v2 and deploy APIM" + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "AML workspace name" + } + }, + "workspaceSku": { + "type": "string", + "metadata": { + "description": "AML workspace SKU" + } + }, + "deployAML": { + "type": "bool", + "metadata": { + "description": "If enable v2 and deploy AML workspace" + } } }, "variables": { @@ -469,7 +523,108 @@ "enabledForDiskEncryption": false, "enabledForTemplateDeployment": false } - } + }, + { + "condition": "[parameters('deployAPIM')]", + "apiVersion": "2019-01-01", + "name": "[parameters('apimName')]", + "location": "[parameters('location')]", + "type": "Microsoft.ApiManagement/service", + "sku": { + "name": "[parameters('apimTier')]", + "capacity": "[parameters('apimCapacity')]" + }, + "properties": { + "publisherEmail": "[parameters('apimAdminEmail')]", + "publisherName": "[parameters('orgName')]" + }, + "resources": [ + { + "condition": "[parameters('deployAPIM')]", + "apiVersion": "2019-01-01", + "type": "loggers", + "name": "[parameters('apiWebAppInsightsName')]", + "dependsOn": [ + "[concat('Microsoft.ApiManagement/service/', parameters('apimName'))]" + ], + "properties": { + "loggerType": "applicationInsights", + "resourceId": "resourceId('microsoft.insights/components', parameters('apiWebAppInsightsName'))", + "credentials": { + "instrumentationKey": "[reference(concat('microsoft.insights/components/', parameters('apiWebAppInsightsName')), '2015-05-01').InstrumentationKey]" + } + } + }, + { + "condition": "[parameters('deployAPIM')]", + "apiVersion": "2019-01-01", + "type": "diagnostics", + "name": "applicationinsights", + "dependsOn": [ + "[concat('Microsoft.ApiManagement/service/', parameters('apimName'))]", + "[concat('Microsoft.ApiManagement/service/', parameters('apimName'), '/loggers/', parameters('apiWebAppInsightsName'))]" + ], + "properties": { + "loggerId": "[concat('/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ApiManagement/service/', parameters('apimName'), '/loggers/', parameters('apiWebAppInsightsName'))]", + "alwaysLog": "allErrors", + "sampling": { + "percentage": 100, + "samplingType": "fixed" + } + } + } + ] + }, + { + "condition": "[parameters('deployAML')]", + "type": "Microsoft.MachineLearningServices/workspaces", + "apiVersion": "2020-04-01", + "name": "[parameters('workspaceName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", + "[resourceId('Microsoft.Insights/components', parameters('apiWebAppInsightsName'))]" + ], + "tags": {"purpose":"Luna.ai"}, + "sku": { + "tier": "[parameters('workspaceSku')]", + "name": "[parameters('workspaceSku')]" + }, + "identity": { + "type": "systemAssigned" + }, + "properties": { + "friendlyName": "[parameters('workspaceName')]", + "storageAccount": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "keyVault": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", + "applicationInsights": "[resourceId('Microsoft.Insights/components', parameters('apiWebAppInsightsName'))]" + } + }, + { + "condition": "[parameters('deployAML')]", + "type": "Microsoft.MachineLearningServices/workspaces/computes", + "apiVersion": "2020-04-01", + "name": "[concat(parameters('workspaceName'), '/lunaamlcompute')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[resourceId('Microsoft.MachineLearningServices/workspaces', parameters('workspaceName'))]" + ], + "properties": { + "computeType": "AmlCompute", + "computeLocation": "[parameters('location')]", + "properties": { + "vmSize": "STANDARD_DS3_V2", + "vmPriority": "Dedicated", + "scaleSettings": { + "maxNodeCount": 4, + "minNodeCount": 0, + "nodeIdleTimeBeforeScaleDown": "PT2M" + }, + "remoteLoginPortPublicAccess": "Enabled" + } + } + } ], "outputs": { } diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/aml-workspace-failed-run.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/aml-workspace-failed-run.png new file mode 100644 index 0000000..397c32b Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/aml-workspace-failed-run.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/aml-workspace-get-endpoint.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/aml-workspace-get-endpoint.png new file mode 100644 index 0000000..b9f2352 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/aml-workspace-get-endpoint.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aad-application-overview.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aad-application-overview.png new file mode 100644 index 0000000..8856789 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aad-application-overview.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aad-application-secrets.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aad-application-secrets.png new file mode 100644 index 0000000..d53cb25 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aad-application-secrets.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aci.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aci.png new file mode 100644 index 0000000..aa14c47 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aci.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aml-resource-id.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aml-resource-id.png new file mode 100644 index 0000000..d6ca61a Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-aml-resource-id.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-apim-diagnostic.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-apim-diagnostic.png new file mode 100644 index 0000000..766adda Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-apim-diagnostic.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-azure-marketplace.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-azure-marketplace.png new file mode 100644 index 0000000..24d40cc Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-azure-marketplace.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-find-aci.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-find-aci.png new file mode 100644 index 0000000..e3943b7 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-find-aci.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-aad.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-aad.png new file mode 100644 index 0000000..c18ffe4 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-aad.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-apim.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-apim.png new file mode 100644 index 0000000..1c76f8d Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-apim.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-saas.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-saas.png new file mode 100644 index 0000000..4ed7de7 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-go-to-saas.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-subscription-id.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-subscription-id.png new file mode 100644 index 0000000..50fa4b0 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-subscription-id.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-tenant-id.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-tenant-id.png new file mode 100644 index 0000000..dd33418 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-tenant-id.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-user-id.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-user-id.png new file mode 100644 index 0000000..15abd91 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-how-to-find-user-id.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-locate-aad-application.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-locate-aad-application.png new file mode 100644 index 0000000..912b5a0 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-locate-aad-application.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-log-analytics-role-assignement.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-log-analytics-role-assignement.png new file mode 100644 index 0000000..8b89b84 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-log-analytics-role-assignement.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-log-analytics.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-log-analytics.png new file mode 100644 index 0000000..f08ccd2 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-log-analytics.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-completed.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-completed.png new file mode 100644 index 0000000..ec9bbfe Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-completed.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-page.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-page.png new file mode 100644 index 0000000..e0c3a3d Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-page.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-subscribe.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-subscribe.png new file mode 100644 index 0000000..88615da Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/azure-portal-saas-offer-subscribe.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/configure-offer-parameters.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/configure-offer-parameters.png new file mode 100644 index 0000000..59d27f0 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/configure-offer-parameters.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/configure-webhooks.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/configure-webhooks.png new file mode 100644 index 0000000..7e751b4 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/configure-webhooks.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/copy-webhook-urls.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/copy-webhook-urls.png new file mode 100644 index 0000000..ad09bb8 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/copy-webhook-urls.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-plan.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-plan.png new file mode 100644 index 0000000..63ec87e Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-plan.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-deployment.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-deployment.png new file mode 100644 index 0000000..675e305 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-deployment.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-product.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-product.png new file mode 100644 index 0000000..cdb0039 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-product.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-version-1.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-version-1.png new file mode 100644 index 0000000..7517647 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-rtp-version-1.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-saas-offer.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-saas-offer.png new file mode 100644 index 0000000..1ce1b0f Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-saas-offer.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-deployment.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-deployment.png new file mode 100644 index 0000000..b60b294 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-deployment.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-product.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-product.png new file mode 100644 index 0000000..b24abf5 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-product.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-version-1-old.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-version-1-old.png new file mode 100644 index 0000000..a42e221 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/create-new-tyom-version-1-old.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-landing-page.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-landing-page.png new file mode 100644 index 0000000..b6b8d56 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-landing-page.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-portal-custom-meters.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-portal-custom-meters.png new file mode 100644 index 0000000..37d5964 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-portal-custom-meters.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-user-portal-subscription-details.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-user-portal-subscription-details.png new file mode 100644 index 0000000..cf1e950 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-user-portal-subscription-details.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-user-subscription-list.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-user-subscription-list.png new file mode 100644 index 0000000..6de2540 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/luna-user-subscription-list.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/partner-center-plan-meter.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/partner-center-plan-meter.png new file mode 100644 index 0000000..3d52992 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/partner-center-plan-meter.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/register-aml-workspace.png b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/register-aml-workspace.png new file mode 100644 index 0000000..41659c9 Binary files /dev/null and b/end-to-end-solutions/Luna/Resources/Documentation/images/luna.ai/register-aml-workspace.png differ diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/README.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/README.md new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/README.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/README.md new file mode 100644 index 0000000..f602a3e --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/README.md @@ -0,0 +1,51 @@ +# Publish your Machine Learning models as Azure SaaS service using Project Luna + +## Overview + +Project Luna is a service template which helps Microsoft partners package and publish Machine Learning models as Azure SaaS services and enable transact through Microsoft without writing extra code. + +With project Luna, you can focus on developing the key values which is the Machine Learning models, and enable it as a sellable service in Azure platform without becoming an Azure expert. + +In this tutorial, we will show you how to use Project Luna, Azure Machine Learning Service and Azure Marketplace to package and publish ML models and algorithms into AI services and sell through Microsoft. + +We are going to use a simple sklearn Logistic Regression classification model as an example. By end of the tutorial, you will have: + +- An Azure Marketplace SaaS offer which allows user to train and use Logistic Regression classification models through API calls or client library. +- Two plans in your SaaS offer: + - First plan provides real-time prediction AI service. It exposing an service endpoint with a pre-trained logistic regression classification model using the sklearn iris dataset. + - Second plan provides a train-your-own-model AI service. It allows user to train classification models using their own data, use the model to do batch inference or deploy the model to a service endpoint for real-time scoring +- Usage based billing enabled for the train-your-own-model plan. + +## Get Started + +- [Get ready to start](./get-ready.md) +- [Deploy Luna service to your Azure subscription](./setup-luna.md) + +## Work on your code + +- [Create a ML project using Luna.AI project template](./use-luna-ml-project-template.md) +- [Add your code to the ML project](./add-ml-code.md) +- [Train and deploy a model using sklearn Iris sample data](./deploy-pre-trained-model.md) +- [Test and publish Azure Machine Learning pipelines](./use-luna-ml-project-template.md) + +## Publish an AI service + +- [Publish an AI service](./publish-ai-service.md) +- [Test AI service](./test-ai-service.md) + +## Publish a SaaS offer + +- [Publish an SaaS offer](./publish-saas-offer.md) +- [Send a welcome letter using webhook](./send-welcome-letter-using-webhook.md) +- [Test SaaS offer](./test-ai-service.md) +- [Config usage based billing](./config-meter-based-billing.md) + +## Management and Maintenance + +- [Deploy and publish a hotfix](./deploy-a-hotfix.md) +- [Deploy and publish a breaking change with version bump](./deploy-a-version-bump.md) + +## Summary + +- [Responsible AI Overview](./responsible-ai-overview.md) +- [Summary](./summary.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/add-ml-code.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/add-ml-code.md new file mode 100644 index 0000000..0e8d9e1 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/add-ml-code.md @@ -0,0 +1,166 @@ +# Add code to the Luna ML project + +In this article, we are going to show you how to add training, batch inference and scoring code to the Luna ML project. We will use a sklearn Logistic Regression classification model as an example. + +## Update conda environment + +Luna and Azure Machine Learning service will create the execution environment from the *conda.yml* file in the base folder of Luna project template. You should add your conda or pip dependencies in the file. + +In this tutorial, we will add the sklearn pip package: + +```yaml +- pip: + ... + sklearn +``` + +## Create conda environment for local test + +In this tutorial, we will run some test locally before deploying the code to Azure Machine Learning service. You need to open a anaconda console, run the following command from the root folder of the Luna ML project template to create the local conda environment after you update your conda.yml file: + +```shell +conda env create -f conda.yml +conda activate project_environment +``` + +## Where to add my code + +The only source code file you need to update in the Luna ML project template is *src/luna_publish/LunaPythonModel.py*. There're 5 functions in the LunaPythonModel class: + +The following two functions are used for real-time prediction + +- load_context: this function will be called every time the container instance/pod started if you or the user deploy the model to a service endpoint. You can use this function to perform some heavy initialization operations. +- predict: this function will be called when user calling the deploy service endpoint API for real-time scoring. + +The following two functions are used for model training and batch inference + +- train: the function to train a model +- batch_inference: the function to perform batch inference using a model +- set_run_mode: this function is for Luna service usage only. Please don't update or remove it. + +## Import modules + +You need to add required modules to the *src/luna_publish/LunaPythonModel.py*. In this tutorial, we will be training a Logistic Regression model using sklearn. Add the followings to the code: + +```python +from sklearn.linear_model import LogisticRegression +import pandas as pd +import json +import os +import pickle +import requests + +from luna.numpyJsonEncoder import NumpyJSONEncoder +``` + +The NumpyJSONEncoder helps you serialize Numpy data types to JSON strings. You can also implement and use your own JSON encoder. + +## Model Training + +You can add following code to the *train* function of LunaPythonModel class to train a sklearn Logistic Regression classification model: + +```python +train_data = pd.read_csv(user_input["trainingDataSource"]) + +label_column_name = user_input['labelColumnName'] if 'labelColumnName' in user_input else train_data.columns[-1] +description = user_input['description'] if 'description' in user_input else 'this is my model description' + +X = train_data.drop([label_column_name], axis=1) + +Y = train_data[label_column_name] + +log_reg = LogisticRegression() +log_reg.fit(X, Y) + +model_path = 'models' +model_file = os.path.join(model_path, "model.pkl") +pickle.dump(log_reg, open(model_file, 'wb')) + +return model_path, description +``` + +The user_input is an dictionary contains the JSON contain from user API request. In this case, a sample user input will be: + +```json +{ + "trainingDataSource": "https://xiwutestai.blob.core.windows.net/lunav2/Iris/data.csv?your_sas_key", + "labelColumnName": "medv", + "description": "boston housing price prediction" +} +``` + +The function will read training data from *trainingDataSource* and train a Logistic Regression model using sklean library. + +After you trained and vaidated the model, you can save the model file/files to a local folder (defined as model_path). Luna service will automatically register the model to the Azure Machine Learning workspace with an auto generated model id and return the model id to the user. + +## Batch Inference + +You can add the following code to the batch_inference function to perform batch inference using a Logistic Regression classification model: + +```python +data = pd.read_csv(user_input["dataSource"]) +output_filename = user_input["output"] + +model_file = os.path.join(model_path, "models", "model.pkl") +model = pickle.load(open(model_file, 'rb')) + +y_proba = model.predict(data) + +temp_filename = "imputation_result.csv" +with open(temp_filename, "wt") as temp_file: + pd.DataFrame(y_proba).to_csv(temp_file, header=False) + +with open(temp_filename , 'rb') as fh: + response = requests.put(output_filename, + data=fh, + headers={ + 'content-type': 'text/csv', + 'x-ms-blob-type': 'BlockBlob' + } + ) + +return +``` + +A sample user input will look like: + +```json +{ + "dataSource": "https://xiwutestai.blob.core.windows.net/lunav2/Iris/test.csv?your_sas_key", + "output": "https://xiwutestai.blob.core.windows.net/lunav2/Iris/result.csv?your_sas_key" +} +``` + +Luna service will pre-download the model based on user provided model id and save the model files in *model_path* folder. + +The function will read the data from the *dataSource*, predict the labels, save the result to a local file and upload it to the *output* Azure blob. + +## Real-time scoring + +The model can be deployed to a service endpoint (AKS or Azure Container Instances) for real-time scoring. There're two functions to update for real-time scoring: + +- If you need to run any code every time when the container or the service started, add it to the *load_context* function. +- If you need to run the code for each user scoring request, add it to the *predict* function. + +In this tutorial, you can add the following code to *load_context* function to load the model into memory and save it in *_model* property: + +```python +model_file = os.path.join(model_path, 'models/model.pkl') +self._model = pickle.load( open( model_file, "rb" ) ) +return +``` + +Then you can add the following code to the *predict* function to predict the label using the model: + +```python +user_input = json.loads(model_input) + +scoring_result = {"result": self._model.predict(user_input["records"])} + +scoring_result = json.dumps(scoring_result, cls=NumpyJSONEncoder) +return AMLResponse(scoring_result, 200) +``` + +## Next Step + +[Train and deploy a model using sklearn Iris sample data](./deploy-pre-trained-model.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/config-meter-based-billing.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/config-meter-based-billing.md new file mode 100644 index 0000000..1ca1119 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/config-meter-based-billing.md @@ -0,0 +1,84 @@ +# Configure usage based billing + +In this article, we are going to show you how to configure usage based billing in Luna service for your SaaS offer. + +When we created the SaaS offer in Azure Marketplace, we configured the a meter to bill the user $0 per 1000 API calls. We are going to continue to use this meter to configure usage based billing. + +In Luna service, we assume all the billing events can be generated by aggregating the telemetry data. In this sample, we will collect the API management gateway log into Azure Log Analytics and configure Luna service to query the telemetry data, generate and send the billing events. + +## Create a Log Analytics workspace + +You can following [this instruction](https://docs.microsoft.com/en-us/azure/azure-monitor/learn/quick-create-workspace) to create a Log Analytics workspace. + +## Collect API management Telemetry data + +After the Log Analytics workspace is created, we need to configure the API management service to send the telemetry data to the Log Analytics workspace. + +First login into Azure portal, type in uniqueName-apim in the search textbox and select the service. + +![azure-portal-go-to-apim](../../images/luna.ai/azure-portal-go-to-apim.png) + +On the APIM resource page, select "Diagnostic settings" under "Monitoring" in the menu on the left side. + +![azure-portal-apim-diagnostic](../../images/luna.ai/azure-portal-apim-diagnostic.png) + +Click on "Add diagnostic setting" on the page. It will open the diagnostic setting details page. On this page, give the setting a name, check at least "GatewayLogs" under "log", and check "Send to Log Analytics" under "Destination details". Then select the Log Analytics workspace you just created and click on "Save" button. + +Now API Management service will start to send telemetry data in the categories you checked to your Log Analytics workspace. + +## Configure access to Log Analytics workspace + +Now we need to configure the access to Log Analytics workspace from Luna service. + +- Go back to the Log Analytics workspace in Azure portal by type in the name in the search text box. Select "Access Control" in the menu on the left side. + + ![azure-portal-log-analytics](../../images/luna.ai/azure-portal-log-analytics.png) + +- Click on "Add" button and choose "Add role assignement" +- On the popped up side window, choose "Log Analytics Reader" as role and in the "Select" text box, type in "uniqueName-AzureResourceManager-aad" where uniqueName is the unique name you used when deploying Luna service. Choosed the filtered item and click on "Save". Here in this example, we are reusing the AAD application we created for Luna service to deploy ARM templates to your Azure subscription. If you want to create a new AAD application for this purpose, you can follow the instruction [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal). + + ![azure-portal-log-analytics-role-assignement](../../images/luna.ai/azure-portal-log-analytics-role-assignement.png) + +- Go back to the overview page of the workspace and write down the Workspace ID + +## Configure custom meter in Luna management portal + +Login into the Luna management portal and click on the little pencil button on your offer to open the offer configuration wizard. Then go to the Meter tab. + +Click the "+ Add" button under telemetry data connectors" to add a new entry. Give it a name, choose "LogAnalytics" as type and put the following JSON string in the "Configuration" text field: + +```json +{ + "WorkspaceId": "workspace-id", + "AADClientId": "aad-client-id", + "TenantId": "aad-tenant-id", + "KeyVaultName": "uniqueName-keyvault", + "AADSecretName": "arm-app-key" +} +``` + +where workspace_id is the one your just saved in the previous step. If you are reusing the AAD application in the previous step, the aad-client-id and aad-tenant-id is the ones you got when your are [registering AML workspace](./publish-ai-service.md#Register-a-Azure-Machine-Learning-workspace) in a previous step, and the uniqueName is the unique name you used when you were deploying Luna service. Otherwise, you need to get the client id, tenant id from the new created AAD application, save the client secret in the key vault and update the AADSecretName as well. + +Then you can click on save to save the update. This will tell Luna service how to connect to your Log Analytics workspace. + +Click on "+ Add" button under "Custom Meters" to add a new entry. The meter name here has to be the same with the meter name you used when configuring the plan in the Azure Marketplace SaaS offer (TODO: add link). Choose the telemetry data connector you just created in the dropdown list, put the following Log Analytics query in the "Query" text box and click on "Save" button to save the changes. + +```text +ApiManagementGatewayLogs +| where ApimSubscriptionId != "" and ApimSubscriptionId != "master" +| summarize quantity=toreal(count(CorrelationId))/1000 by resourceId = ApimSubscriptionId +``` + +![luna-portal-custom-meters](../../images/luna.ai/luna-portal-custom-meters.png) + +## Update plan to use the custom meter + +Now you need to tell Luna service which plan is using this custom meter. + +Go to the "Plan" tab and click on the pencil button on your plan to open the "Modify Plan" window. In the pop up window, click on the "+ Add" button under "Custom Meters", choose the meter you just created and click on "Save". + +Now Luna service will automatically run the telemetry query against the Log Analytics workspace, aggregate the result and send it to Azure Marketplace as meter events. + +## Next Step + +[Deploy and publish a hotfix](./deploy-a-hotfix.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-a-hotfix.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-a-hotfix.md new file mode 100644 index 0000000..adfdbb9 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-a-hotfix.md @@ -0,0 +1,22 @@ +# Deploy and publish a hotfix + +Previously in this tutorial, we already published and tested our Logistic Regression model as an Azure SaaS offer. In this ariticle, we will show you how can you apply a hotfix to a published AI service. + +Publishing a hotfix means: + +- It is a critical fix, you want everyone to get it as soon as possible +- Your users' production code should continue to work without any change + +In Luna service, you can deploy and publish a hotifx by the following steps: + +- Test and check in your code change to your Git repo +- If it is a real-time prediction product, train and deploy the model. Review [this document](./deploy-pre-trained-model.md) for more details. +- If it is a train-your-own-model product, test and publish new Azure Machine Learning pipeline endpoints. Review [this document](./test-and-publish-aml-pipelines.md) for more details. +- In Luna management portal, find the product, deployment and API version you are trying to apply the hotfix to +- Update the real-time prediction API or the pipeline ids and save the changes + +Now your hotfix is deployed and available to all the users who is using this API version. + +## Next Step + +[Deploy and publish a breaking change with version bump](./deploy-a-version-bump.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-a-version-bump.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-a-version-bump.md new file mode 100644 index 0000000..f9d2f2b --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-a-version-bump.md @@ -0,0 +1,17 @@ +# Deploy a breaking change with version bump + +In the previous article, we showed you how to deploy and publish a hotfix to your AI service. In this article, we are going to show you how to update your service when you are introducing a breaking change. + +When you are introducing a breaking change or a major feature, you don't want to break your users' product code. In Luna, we allow you deploy this type of changes with a version bump: + +- Test and check in your code change to your Git repo +- If it is a real-time prediction product, train and deploy the model. Review [this document](./deploy-pre-trained-model.md) for more details. +- If it is a train-your-own-model product, test and publish new Azure Machine Learning pipeline endpoints. Review [this document](./test-and-publish-aml-pipelines.md) for more details. +- In Luna management portal, find the product and deployment +- Instead of updating an existing API version, create a new API version using the new real-time prediction endpoint URL or the AML pipelines. Review [this document](./publish-ai-service.md) for more details + +Now you have a new API version published. All your existing users will continue running the AI service with the original vesion without any impact. When they are ready to test and move to the new version, they can simple do that by updating the api-version query parameter in the requests sent to your AI service. + +## Next Step + +[Responsible AI Overview](./responsible-ai-overview.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-pre-trained-model.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-pre-trained-model.md new file mode 100644 index 0000000..436477e --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/deploy-pre-trained-model.md @@ -0,0 +1,128 @@ +# Train and deploy a model using sklearn Iris sample data + +In this article, we are going to show you how can you train a model and deploy it to a service endpoint in Azure Machine Learning service. + +You need to run this if you want to pubish a pre-trained model as a Luna AI service later. If you always require your customer training models using their own data and settings, you can skip this article and jump to [Test and publish Azure Machine Learning pipelines](./use-luna-ml-project-template.md) + +## Where's the code + +The code to train and deploy a model is located in */src/luna_publish/azureml/train_and_deploy_model.py* + +## Model id, endpoint id and more + +Luna service will run the model training code and automatically register the model in Azure Machine Learning service. To register and deploy the model, you need to provide the following properties: + +- experimentName: the name of Azure Machine Learning experiment +- modelId: the model id will be used to register and identify the model in Azure Machine Learning service +- endpointId: the endpoint id will be used to create and identify the service endpoint where the model is deployed to in Azure Machine Learning service +- serviceEndpointDnsNameLabel: the DNS name label for the deployed service endpoint. + - If you are deploying to Azure Container Instance, the url will look like: + + ```url + http://..azurecontainer.io/score + ``` + + - If you are deploying to AKS, the url will look like: + + ```url + http:///api/v1/service//score + ``` + +All these values need to be a string contains lower case letters, number and dash, starts and ends with a letter. The max length of experiment name, model id and endpoint id is 32. The max lenght of service ednpoint DNS name label is 64. + +## Input data + +You need to define the input data to train the model. In this tutorial, our training function takes 3 parameters: + +- trainingDataSource: the blob storage url with SaS key for the training data +- labelColumnName: the name of the label column. Will use the last column is not specified. +- description: the description of the model + +Following is an example of the input data for our Logistic Regression model: + +```json +{ + "trainingDataSource": "https://xiwutestai.blob.core.windows.net/lunav2/iris/iris.csv?st=2020-07-22T17%3A19%3A10Z&se=2027-10-12T17%3A19%3A00Z&sp=rl&sv=2018-03-28&sr=b&sig=7c%2BaoI8QtdepDHKqJqjjljdBUyDyuL8wbKol2Kn7xaI%3D", + "description": "Iris prediction" +} +``` + +## Run python script to train and deploy model + +You can run the following command in the conda console to train and deploy the model. + +```shell +python src/luna_publish/azureml/train_and_deploy_model.py +``` + +You can specify the following arguments: + +- *--experiment_name*: the name of AML experiment. The default value is *train_and_deploy_model* +- *--model_id*: the id of the registered model. If not specified, a new uuid4 will be used +- *--endpoint_id*: the id of the deployed endpoint. If not specified, a new uuid4 will be used +- *--dns_name_label*: the dns name label of the deployed endpoint. If not specified, a new uuid4 will be used +- *--input_data_file_path*: the training input data file path. The default value is *training_input.json* + +For example: + +```shell +python src/luna_publish/azureml/train_and_deploy_model.py --model_id iris_model --endpoint_id iris_endpoint --dns_name_label iris_classification +``` + +The script will: + +- Start a AML pipeline run to train the model +- Register the model with modelId +- Deploy the model to specified type of service endpoint +- Print out the scoring URL and primary authentication key + +Record the scoring URL and key. + +## Troubleshoot issues + +The script will print out *"Link to Azure Machine Learning Portal"* for the pipeline run. If the run failed, you can open the link and see the error details in Azure Machine Learning workspace. You can find the most relevant error messages by click on the failed component on the graphic design canvas. You can also browse the azureml-logs folder to see other logs. + +![aml-workspace-failed-run](../../images/luna.ai/aml-workspace-failed-run.png) + +If the deployment step failed and you need more information from the container log to toubleshoot, you can find it in the Azure portal + +- Go to azure portal and type in the deployment endpointId in the search textbox. Select the Container instance popped up. + + ![azure-portal-find-aci](../../images/luna.ai/azure-portal-find-aci.png) + +- Click on "Containers" in the manu on the left side, select the container with same name with the endpointId in the container list and click on "Logs" in the menu bar below. You will see all the log from the container instance. + + ![azure-portal-aci](../../images/luna.ai/azure-portal-aci.png) + +- You can also go the the "Connect" tab and connect to the running container instance for further troubleshooting. + +## Test the scoring endpoint + +After the training and deployment completed, you can find the endpoint information in your Azure Machine Learning workspace. + +- Open the Azure Machine Learning workspace by the pipeline run URL +- Click on "Endpoints" in the menu on left side. Select the endpoint you just deployed +- Go to the "Consume" tab, you will see the REST endpoint and authentiation key + +![aml-workspace-get-endpoint](../../images/luna.ai/aml-workspace-get-endpoint.png) + +You can test the scoring endpoint with your test data in Postman: + +- Open Postman +- Add a new request +- Change the http function to POST +- Paste the scoring URL to the request URL text box +- Click on the Authentication tab, choose "Bearer Token" as Type, paste the key in the "Token" text box +- Click on the Body tab, select "raw", in the content-type dropdown list (default shown as "Text"), choose "JSON" +- Add you test data in the text field. In this tutorial, the scoring function take a 2D array as input with property name "records". Following is an example of the input + + ```json + {"records":[[5.1,3.5,1.4,0.2]]} + ``` + +- Click on "Send" button to send the request +- The response body will show in the bottom. If everything runs correctly, the sample test data should get you [1] as result. + +## Next Step + +[Test and publish Azure Machine Learning pipelines](./test-and-publish-aml-pipeline.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/get-ready.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/get-ready.md new file mode 100644 index 0000000..2ab8de2 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/get-ready.md @@ -0,0 +1,121 @@ +# Get Ready to Start + +You need to get a few things ready before you can start this tutorial: + +## Windows and Windows PowerShell + +You can run most of the tutorial in any modern OS. But since AAD PowerShell module is not supported in .netcore version of Azure PowerShell, you will need a Windows machine with Windows PowerShell installed to deploy Luna service. + +The easiest way to get a Windows Machine is to create a Windows 10 VM in your Azure Subscription. You can follow this document to create your Windows 10 VM: [Create a Windows Virtual Machine in Azure](https://docs.microsoft.com/en-us/learn/modules/create-windows-virtual-machine-in-azure/). Since we are only going to run a PowerShell script, you can choose the minimum configuration and ignore all advanced settings. Make sure you have RDP enabled. + +If Windows PowerShell is not installed on your machine, install it following this [instruction](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-windows-powershell?view=powershell-6). + +Then start a Windows PowerShell window with Administrator permission and run the following command + +```PowerShell +Set-ExecutionPolicy -ExecutionPolicy unrestricted + +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +``` + +Choose *A* to change the policy to Yes to All. + +Then run the following commands to install PowerShell modules: + +```PowerShell +Install-Module -Name Az -AllowClobber + +Install-Module -Name AzureAD -AllowClobber + +Install-Module -Name sqlserver -AllowClobber +``` + +After all these, reboot the VM or machine. + +## Azure Subscription + +In this tutorial, the Luna service and your models will all be deployed to an Azure subscription. You need to make sure: + +- You are a contributor or owner of this Azure subscription +- The following resource providers are enabled in this subscription + - Microsoft.Network + - Microsoft.Compute + - Microsoft.ContainerInstance + - Microsoft.ContainerService + - Microsoft.Insights + - Microsoft.Sql + - Microsoft.MachineLearningServices + - Microsoft.Storage + - Microsoft.ApiManagement + - Microsoft.KeyVault + - Microsoft.Web + - Microsoft.OperationalInsights +- The region where you want to deploy Luna service to is enabled in this subscription + +You can find a PowerShell script [CheckPermissions.ps1](../../../Deployment/CheckPermissions.ps1) under *Resources/Deployment* folder of the this repo. You can enable all resource providers, check your permission and region availability by running + +```powershell +./CheckPermissions.ps1 -tenantId -subscriptionId -userId -location +``` + +where + +- the tenant-id is the tenant id of your organization +- the subscription-id is the id of your Azure subscription +- the user-id is your AAD id +- the azure-region is in a format like "West US 2". + +See [this document](../how-to/how-to-find-azure-info.md) about how to find those information in Azure portal. + +## Azure Active Directory + +Azure Active Directory (AAD) authentication is used in Luna services and through this tutorial. You need to make sure: + +- Have access to your orgnization's Azure Active Directory +- Have permission to register AAD applications + +You can test your permission by running the following PowerShell command + +```powershell +Connect-AzureAD -TenantId $tenantId + +$app = New-AzureADApplication -DisplayName 'testAADApp' + +Remove-AzureADApplication -ObjectId $app.ObjectId +``` + +It will try to create an AAD application and then delete it. + +## Python environment and conda + +You will need to prepare your python environment for local testing. + +You can install conda following [this instruction](https://docs.conda.io/projects/conda/en/latest/user-guide/install/). + +## Visual Studio Code or other IDE + +If you don't have any IDE installed on your machine to develop you ML project yet, we'd recommend you install [Visual Studio Code](https://code.visualstudio.com/). + +## Git repo + +A Git repo allows you manage your ML project and code. It is not required but highly recommened. + +The easiest way to get a Git repo is to register and create a repo in GitHub.com. Later in this tutorial, we will show you how to create a GitHub repo using Luna ML project template. + +## Azure Storage Account + +In this tutorial, the AI service will read and write data from a Azure storage account. If you don't have a Azure storage account, you can follow [the instruction here](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal) to create one. + +We also highly recommend downloading and installing the [Azure Storage Explorer](https://azure.microsoft.com/en-us/features/storage-explorer/). The Azure Storage Explorer can help you easily create, manage and browse blob files in your Azure storage. + +## Postman + +In this tutorial, we will be testing the AI services by calling REST APIs. For you convinience, we recommed you [install Postman](https://www.postman.com) on your dev machine. + +## Notepad or other tools to record information + +Scripts and commands we are running in this tutorial may generate information which is needed for futher steps. We recommend you saving those information using Windows Notepad or your favirate text editing tools. + +## Next Step + +- [Deploy Luna service to your Azure subscription](./setup-luna.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/publish-ai-service.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/publish-ai-service.md new file mode 100644 index 0000000..1ca43e9 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/publish-ai-service.md @@ -0,0 +1,111 @@ +# Publish an AI service + +In this article, we will show you how to publish an AI service using Luna management portal using the service endpoint and AML pipelines we published in the previous steps. + +## Access Luna management portal + +You can access the Luna management portal at https://*uniqueName*-isvapp.azurewebsites.net/ where *uniqueName* is the unique name you picked when you deploying Luna service. You will need to log in the portal using you AAD account. The AAD account must be added as the admin account during the deployment. + +## Publish a real time prediction service + +A real time prediction service allows the end user to call the endpoint API for real time scoring using a pre-trained model. In this example, we will use the Logistic Regression model we trained earlier using the Iris sample data. + +You can skip this section if you are not planning to publish a real time prediction service. + +### Create a real-time prediction product + +You can create a new AI product by click on the "New Product" button on the "Products" tab. You will need to provide the following information: + +- Id: the product id. In this example, we are going to use rtp_product +- Product Type: the type of the product, choose Real-time Prediction +- Host Type: choose SaaS. "Bring Your Own Compute" host type is not supported yet. +- Owner: use your AAD id + +![create-rtp-product](../../images/luna.ai/create-new-rtp-product.png) + +Then click on "Save" button to save the changes. + +### Create a real-time prediction deployment + +After the product is created, you will see two sections on the "Product Details" page: Deployments and AML Workspaces. For a real-time prediction AI service, you don't need to register an AML workspace yet. We will cover that in the next section when creating the train your own model service. + +To create a new deployment, click on the "New Deployment" button. You will need to provide the following information: + +- Deployment Name: the name of the deployment. We are going to use *rtp_deployment* in this example. +- Description: the description of the deployment, you can write your own description here + +![create-rtp-deployment](../../images/luna.ai/create-new-rtp-deployment.png) + +Then click on "Create" button to save the changes. + +### Create a real-time prediction API version + +In Luna AI service, we allow you to create multiple version of APIs for a deployment. To create a new API version, click on the "New Version" button. + +![create-rtp-version](../../images/luna.ai/create-new-rtp-version-1.png) + +## Publish a train-your-own-model AI service + +A train-your-own-model AI service allows the end user to call the APIs to train models using their own data, do batch inference with their own models and/or deploy the models to real time service endpoint for their online applications. + +You can skip this section if you are not planning to publish a train-your-own-model AI service. + +### Create a train-your-own-model product + +We are going to create a new product with name tyom_product for the train your own model AI service. For "Product Type", you should choose "Train Your Own Model" + +![create-tyom-product](../../images/luna.ai/create-new-tyom-product.png) + +Then click on the "Save" button to save the changes. + +### Register a Azure Machine Learning workspace + +In a "Train Your Own Model" AI service, the model training, batch inference and deployment are running in an Azure Machine Learning workspace as pipeline runs. We already published the AML pipelines in the previous step (TODO: add link). To create the AI service, you need to provide the information about your Azure Machine Learning workspace and how to connect to it. + +In Luna service, we use an AAD application to connect to the Azure Machine Learning service. In this example, we will reuse an AAD application we created when deploying Luna service. + +First, in Azure portal type in "aad" in the search text box and click on "Azure Active Directory": + +![azure-portal-go-toaad](../../images/luna.ai/azure-portal-go-to-aad.png) + +In the menu on the left side, choose "App registration". Click on "All application", type in *uniqueName*-azureresourcemanager-aad, and click on the item. + +![azure-portal-locate-aad-application](../../images/luna.ai/azure-portal-locate-aad-application.png) + +On the overview page of the AAD application, you can find the "Application (client) ID" and "Directory (tenant) ID". Write it down in the notepad + +![azure-portal-aad-application-overview](../images/luna.ai/azure-portal-aad-application-overview.png) + +Then click on "Certificates & Secrets" in the menu on the left side. Under "Client Secrets", click on "New client secret". Write your own description and click on "Add". Copy and save the secret value before you leave the page. + +![azure-portal-aad-application-secret](../../images/luna.ai/azure-portal-aad-application-secrets.png) + +Type in the name of you Azure Machine Learning service in the search window of Azure portal, find your Azure Machine Learning service and click on it. + +Click on access control + +![register-aml-workspace](../../images/luna.ai/register-aml-workspace.png) + +[Azure Role-based access control documentation](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal) + +### Create a train-your-own-model deployment + +You can create a new deployment in your product by click on the "New Deployment" button. Put in deployment name typm_deployment, write your own description and click on "Create" button + +![create-tyom-deployment](../../images/luna.ai/create-new-tyom-deployment.png) + +### Create a train-your-own-model API version + +After the deployment is created, you can create a new API version by click on the "New Version" button. In the popped up window, put in "v1.0" as version name. TODO: finish when UI change is deployed. + +![create-tyom-version](../../images/luna.ai/create-new-tyom-version-1-old.png) + +## Save the Luna webhook URL + +Going back to the home page of the "Products" tab, you will see a button "Copy Luna webhook URL". Clicking on the button, it will open a modal where 3 webhook URLs are shown. Later we will use these URLs to manage subscriptions of the AI services you just published in the SaaS offer. You may want to save the URLs in a notepad so you don't have to come back to this page later. + +![copy-webhook-urls](../../images/luna.ai/copy-webhook-urls.png) + +## Next Step + +[Test AI service](./test-ai-service.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/publish-saas-offer.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/publish-saas-offer.md new file mode 100644 index 0000000..824ded3 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/publish-saas-offer.md @@ -0,0 +1,132 @@ +# Publish an SaaS offer + +In this article, we will show you how to publish the AI service we created in the previous steps as an Azure SaaS offer in Azure Marketplace. + +## (Optional) Publish an SaaS offer in Azure Marketplace + +To test the fully end to end user experience, you need to publish an SaaS offer in Azure Marketplace. This requires you become a Microsoft Partner first, and get all marketing and legal documents ready. If you are not ready to create and publish your offer in Azure Marketplace yet, you can skip this step for now. Later we will also show you how to test the SaaS offer without being published in Azure Marketplace. + +You can follow [this instruction](https://docs.microsoft.com/en-us/azure/marketplace/partner-center-portal/create-new-saas-offer) to create and publish your SaaS offer in Azure Marketplace. To connect your Azure Marketplace SaaS offer with Luna service, you need to: + +- Choose "yes" for "Would you like to sell through Microsoft?" in the offer setup +- In "Technical configuration" use the information the deployment script printed out when you were deploying Luna service. + +To use your offer for this tutorial, you need to: + +- Create a private plan. +- Set $0 monthly flat rate so you don't really get billed for your test. +- Add your orgnization's tenant id to the Restricted Audience list of your private plan. +- Configure [meter based billing](https://docs.microsoft.com/en-us/azure/marketplace/partner-center-portal/saas-metered-billing) for your private plan. In this tutorial, we will assume you billing your user $0 per 1000 API calls. + + ![partner-center-plan-meter](../../images/luna.ai/partner-center-plan-meter.png) + +## Create and configure an offer in Luna management portal + +To create an SaaS offer in Luna, you can login to the Luna management portal (where you published the AI service earlier), go to the "Offers" tab and click on "New Offer". You need to provide the following info: + +- ID: the offer id. If you created an SaaS offer in Azure Marketplace, you will need to use the same offer id +- Alias: a friendly name for your offer +- Version: the version of your offer. +- Owners: put your AAD id here +- Host Subscription: Luna allows you deploy and manage Azure resources when end user create, update or delete their subscription. We won't use this feature in our tutorial, so you can put any GUID in this field. If you are going to explore and use this feature later, you can alway come back and update your subscription id. + +![create new offer](../../images/luna.ai/create-new-saas-offer.png) + +Then click on Save. It will bring you to the offer configuration wizard. + +### Add offer parameters + +The offer parameters allow you to collect additional information from your customer when they creating the SaaS offer subscription. In this tutorial, we are going to add 3 offer parameters: + +- Service type: let user choose either they want to subscibe the real-time prediction service or train their own model + - parameter Id: servicetype + - Displace Name: Service Type + - Description: Choose which type of service you want to subcribe + - Value Type: String + - From List: checked + - Value List: real-time prediction;train your own model +- Email: ask user to provide their email address + - parameter Id: email + - Displace Name: Email + - Description: Your email address + - Value Type: String + - From List: unchecked +- Receive Product Update: ask user whether they want to get email about your product update + - parameter Id: productupdate + - Displace Name: Receive Product Update Email? + - Description: Choose whether you want to receive product update email from us + - Value Type: Boolean + +![configure offer parameters](../../images/luna.ai/configure-offer-parameters.png) + +### Create webhooks to connect your SaaS offer with the AI services + +In Luna service, you can define webhooks and calling those webhooks when user create, update or delete their subscriptions. We will use this function to connect your SaaS offer with the AI services you created in the previous steps. + +In the [previous step](./publish-ai-service.md#save-the-luna-webhook-url), we saved 3 webhook URLs from the "Product" tab of Luna management portal. If you don't have those webhook URLs yet, go to the "Product" panel and copy it. + +When you are defining webhooks in Luna service, you can pass in more information to your webhook using query parameters. There are two types of parameters: system parameter and user defined parameters. + +If you want to refer to a system parameter, for example, subscription id, you can use system$$parameter_name. If you want to create your own parameters, just use any name without '$'. In the subscribe webhook, we are going to use the following parameters: + +- ProductName: productName +- DeploymentName: deploymentName +- UserId: system$$subscriptionOwner +- SubscriptionName: system$$subscriptionId +- SubscriptionId: system$$subscriptionId + +Once you added these query parameters to the URL it should look like: + +```http +https://uniqueName-apiapp.azurewebsites.net/api/apisubscriptions/createwithid?ProductName={productName}&DeploymentName={deploymentName}&UserId={system$$subscriptionOwner}&SubscriptionName={system$$subscriptionId}&SubscriptionId={system$$subscriptionId} +``` + +Update the other two webhook URL too: + +The unsubscribe URL + +```http +https://uniqueName-apiapp.azurewebsites.net/api/apisubscriptions/delete?SubscriptionId={system$$subscriptionId} +``` + +The suspend URL + +```http +https://uniqueName-apiapp.azurewebsites.net/api/apisubscriptions/suspend?SubscriptionId={system$$subscriptionId} +``` + +Then on the webhook panel of the offer configuration wizard, click on "Add Webhook" to add a new webhook, name it as "subscribeAIService" and paste the subscribe URL in "URL" field. Also create the "unsubscribeAIService" and "suspendAIService" webhooks using the corresponding URLs. + +After the webhooks are created, you will find 2 user defined parameters automatically extracted from the URLs and shown in the "parameters" table below. In Luna service, you can define the parameter values using C# expression. In this example, we will define the product name and deployment name according to the user picked service type. Add the following values to the "Value" field in the "Parameters" table: + +ProductName: + +```C# +Parameters["servicetype"].Equals("real-time prediction")?"rtp_product":"tyom_product" +``` + +DeploymentName: + +```C# +Parameters["servicetype"].Equals("real-time prediction")?"rtp_deployment":"tyom_deployment" +``` + +![configure webhooks](../../images/luna.ai/configure-webhooks.png) + +Then click on the "Save" button on the upper right corner to save the changes. + +### Create and configure a plan + +The last step of publishing a SaaS offer is to create a plan. Go to the Plans tab in the offer configuraiton wizard, click on "Add Plan" button. + +- In the popped up window, fill in the plan name which should be the same with the plan id you used when creating the private plan in Microsoft Partner Center. +- Then select the webhook for corresponding operations. +- Click on "Save" button to save the changes. + +![create plan](../../images/luna.ai/create-new-plan.png) + +Now you have your SaaS offer and plan published in Luna service. + +## Next step + +[Test SaaS offer](./test-ai-service.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/responsible-ai-overview.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/responsible-ai-overview.md new file mode 100644 index 0000000..5adb077 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/responsible-ai-overview.md @@ -0,0 +1,7 @@ +# Responsible AI Overivew + +In Microsoft, we are committed to the advancement of AI driven by ethical principles that put people first. As a Microsoft Partner, we highly recommend you learn about Microsoft AI principles, approach and how to pub responsible AI in action from [here](https://www.microsoft.com/ai/responsible-ai). + +## Next Step + +[Summary](./summary.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/send-welcome-letter-using-webhook.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/send-welcome-letter-using-webhook.md new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/setup-luna.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/setup-luna.md new file mode 100644 index 0000000..fb126a2 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/setup-luna.md @@ -0,0 +1,70 @@ +# Deploy Luna service to your Azure Subscription + +In this article, we are going to show you how can you deploy Luna service in your Azure subscription + +## Prepare the deployment environment + +Before you continue, you should follow [this document](./get-ready.md) to setup your development environment. + +## Clone project Luna GitHub repo + +You can find our GitHub repo [here](aka.ms/lunaai) + +See [here](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) about how to clone a GitHub repo. + +## Collect required information for deployment + +You will need to following information for the deployment: + +- Tenant id: The tenant id of your organization. +- Subscription id: The subscription id you want to deploy Luna service to +- Location: Which azure region you want to deploy the Luna service to. It should be in format like "westus2". +- A unique name: it is a string with only **lower case letters** and **less than 12 characters**. It will be used as prefix of all Azure and AAD resources. To avoid any failure during deployment, please make it as unique as possible. +- Admin accounts: the AAD accounts who you want to assign admin permission to your Luna service. It can be mutiple AAD accounts seperated by semicolons. +- (only if you are running the script from your dev machine and using any kind of VPN or proxy service) The IP range of your VPN or proxy service: we can add the IP range to Azure SQL Server firewall rule so you can access your SQL database. We will detect your local ip (without VPN or proxy) and add it to the firewall rule automatically. Contact your network admin for the IP range of your VPN or proxy service. + +See [this document](../how-to/how-to-find-azure-info.md) for more details about how to find these information. + +## Run deployment script + +Open a Windows PowerShell window, locate to the local GitHub repo you just cloned. You can find the deployment script under */Resources/Deployment*. + +Open a notepad and compose the deployment script: + +```powershell +./Deploy.ps1 -uniqueName {unique_name} -location {location} -tenantId {tenant_id} -lunaServiceSubscriptionId {subscription_id} -adminAccounts {adminAccounts} +``` + +If you are using any VPN or proxy service, add the following arguments. You don't need set this if you are running the script from an Azure VM. + +```powershell +-firewallStartIpAddress {start_ip_address} -firewallEndIpAddress {end_ip_address} +``` + +**NOTE: for private preview only** +In the end, add the following parameters to get our private preview build: + +```powershell +-buildLocation "https://github.com/Azure/AIPlatform/raw/luna.ai/end-to-end-solutions/Luna/Resources/Builds/2.0" -sqlScriptFileLocation ".\SqlScripts\v2.0\db_provisioning.sql" -enableV2 true +``` + +Copy the whole command to the WindowsPowerShell window and run it. The AAD sign-in page will pop up twice, once for signing in to Azure and second time for AAD. The deployment may take up to 40 minutes mainly because it take quite long time to create Azure API Management service. + +## Record post deployment service + +If the PowerShell command completed with no error, the following information will be printed in the end: + +```text +Deployment finished successfully. +You will need the following information when creating a SaaS offer in Azure Partner Center: +Landing page URL: https://xxxxxx.azurewebsites.net/LandingPage +Connection Webhook: https://xxxxxx.azurewebsites.net/Webhook +Azure Active Directory tenant ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +Azure Active Directory application ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +``` + +Make sure you save this information in Notepad or other tools. + +## Next Step + +[Create a ML project using Luna.AI project template](./use-luna-ml-project-template.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/summary.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/summary.md new file mode 100644 index 0000000..c530775 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/summary.md @@ -0,0 +1,13 @@ +# Summary + +In this tutorial, we showed you how to package a ML model into an Azure SaaS API service, publish it in Azure Marketplace and enable transact through Microsoft. + +To continue the journey and add more functionality to your AI servcie, please review the following documents: + +- [Project Luna Overview](../../README.md) +- [Luna.ai Overview](../README.md) +- [Luna.ai How-To topics](../how-to/README.md) + +To learn more about Microsoft AI platform: + +- [Azure AI](https://azure.microsoft.com/en-us/overview/ai-platform/) \ No newline at end of file diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-ai-service.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-ai-service.md new file mode 100644 index 0000000..c6caab6 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-ai-service.md @@ -0,0 +1,57 @@ +# Test the AI service + +In this article, we are going to show you how can you test the AI services you published in the previous steps. + +There are two different ways to run the test: calling APIs using Postman collection or using Luna client library + +## Run test using Postman collection + +### Download and configure the Postman collection + +We created a Postman collection which you can use to test your AI service. You can download the collection from [here](https://www.getpostman.com/collections/92eec92e800414e8cece). + +After download the collection, you need to update the variables in the collection. + +#### Must updated variables + +- unique_name: the unique name you used when deploying Luna service +- user_id: your AAD account + +#### Other variables + +You can leave the other variables with the default values if you were following our tutorial and publishing your products, deployments and API versions using the default names we were suggesting. If you used your own name, you need update the corresponding variable values. + +### Run tests + +After updating all the variables, you can start to run the test by running the requests from the top. Few things to pay attention to: + +- All the OPTIONS requests are used as comments. Don't run those requests. +- After model training and deployment, keep trying the get operations until the status is "Completed". +- After the batch inference completed, check the result file uploaded to Azure storage account and make sure the result is correct. + +## Run tests using Luna client library + +You can also choose to test the AI service using Luna client library. + +### Subscribe the AI service + +Before testing the AI service using client library, you need to subscribe the AI service, get the base url and the key. You can still download the [Postman collection](https://www.getpostman.com/collections/92eec92e800414e8cece) and run corresponding requests to subscribe the AI service, or you can subscribe the AI service by the following http request: + +```http +POST https://{{unique_name}}-apiapp.azurewebsites.net/api/apisubscriptions/createwithid?ProductName={{product_name}}&DeploymentName={{deployment_name}}&UserId={{user_id}}&SubscriptionName={{subscription_name}}&SubscriptionId={{subscription_id}} +``` + +where + +- unique_name is the unique name you used when deploying Luna service +- product_name is the product name you used when publish the AI service +- deployment_name is the deployment name you used when publish the AI service +- user_id is your AAD id +- subscsription_name can be any name less than 64 characters. +- subscription_id needs to be a guid without hyphens + +### Run tests in the sample python notebook + +## Next Step + +[Publish an SaaS offer](./publish-saas-offer.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-and-publish-aml-pipelines.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-and-publish-aml-pipelines.md new file mode 100644 index 0000000..0887295 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-and-publish-aml-pipelines.md @@ -0,0 +1,134 @@ +# Test and publish Azure Machine Learning pipelines + +In this article, we will show you how to test your code and publish it into Azure Machine Learning pipeline. Later you will be able to use the published pipeline endpoint to create your AI service. + +You need to run this if you want to pubish a AI service which allows user to train models using their own data, do batch inference using the models and/or deploy models to service endpoints for real-time scoring. If you only want to publish a pre-trained model as a AI service, you can skip this article. + +## Prepare test data + +Before running the test, you need to prepare your test data for training, batch inference and real-time scoring. The test data will simulate the end user input when they are calling the AI service which we will be publishing later in this tutorial. + +### Create a storage container for batch inference result and get Shared Access Signiture (SaS key) + +When we [add your code to the ML project](./add-ml-code.md), we uploaded the batch inference result to the Azure storage as a blob file. In this example, you will need to create a container in your storage account and get a Shared Access Signiture to be able to write files in the container. You can do this either in Azure Storage Explorer or run the following PowerShell commands: + +```powershell +$resourceGroupName = 'my-resource-group' +$storageAccountName = 'my-storage-account' +$containerName = "my-container" + +Connect-AzAccount + +$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName + +$ctx = $storageAccount.Context + +$container = New-AzStorageContainer -Name $containerName -Context $ctx + +$token = New-AzStorageContainerSASToken -Name $containerName -Context $ctx -Permission rwdl + +Write-Host $token +``` + +You can also create the container and get the SaS key in Azure Storage Explorer. See [here](https://docs.microsoft.com/en-us/azure/vs-azure-tools-storage-manage-with-storage-explorer?tabs=windows#account-level-shared-access-signature) for more details. + +### Update test data in test_data.json + +In the Luna project template, the test data is defined in *tests/azureml/test_data.json*. You need to define 3 inputs and 1 output data: + +- training_user_input: it is the input data for the training API call. An example of the training input data for our Logistic Regression model is: + + ```json + { + "trainingDataSource": "https://xiwutestai.blob.core.windows.net/lunav2/iris/iris.csv?st=2020-07-22T17%3A19%3A10Z&se=2027-10-12T17%3A19%3A00Z&sp=rl&sv=2018-03-28&sr=b&sig=7c%2BaoI8QtdepDHKqJqjjljdBUyDyuL8wbKol2Kn7xaI%3D", + "description": "Iris classification model" + } + ``` + +- batch_inference_input: it is the input data for the batch inference API call. An example of the batch inference input data will be: + + ```json + { + "dataSource": "https://xiwutestai.blob.core.windows.net/lunav2/iris/iris_test.csv?st=2020-07-22T20%3A52%3A09Z&se=2031-10-14T20%3A52%3A00Z&sp=rl&sv=2018-03-28&sr=b&sig=Thjj%2BjB4GSvWMIUuqKJLLhYLfJSq4uhf%2B7A5ai6qSoA%3D", + "output": "https://.blob.core.windows.net//iris/result.csv" + } + ``` + +- real_time_scoring_input: it is the input data for the real time scoring. An example of the real time scoring input data for our Iris classification model will be: + + ```json + { + "records":[[5.1,3.5,1.4,0.2]] + } + ``` + +- real_time_scoring_expected_output: it is the expected output of the real time scoring call. We will be comparing it to the real output. An example of the expected output data from the input above will be: + + ```json + { + "result":[1] + } + ``` + +## Run the test + +The test script is located at *tests/azureml/azureml_test.py*. Run the following command in the Anaconda console from the root folder of the project + +```shell +python tests/azureml/azureml_test.py +``` + +If you are using a different test data file, you can specify the test data file path by the *--test_data_file_path* parameter: + +```shell +python tests/azureml/azureml_test.py --test_data_file_path +``` + +The test will: + +- Schedule an AML pipeline run to train a model and regiester the model to AML workspace +- Poll the model training run status by model id and wait until it completed +- Schedule an AML pipeline run to do batch inference using the model we just trained +- Poll the batch inference run status by the operation id and wait until it completed +- Schedule an AML pipeline run to deploy the model to a service endpoint for real-time scoring +- Poll the deploymenet run status by the endpoint id and wait until it completed +- Test the real-time endpoint and compare the result with expected result + +## Troubleshoot issues + +When we start a pipeline run, we will print out the *"Link to Azure Machine Learning Portal"* in the ternimal. If your pipeline run failed, you can click into the link and find out the detailed errors. Fix those errors and schedule the run again. + +![aml-workspace-failed-run](../../images/luna.ai/aml-workspace-failed-run.png) + +## Publish code to AML pipelines + +If all local tests passed, we are ready to publish the code to AML pipelines. The script to publish the pipelines is located at *src/luna_publish/azureml/publish_azureml_pipelines.py*. + +Before running the script, you should update the pipeline names to the values you want. In this example, we will use the following values: + +Run the following command in the Anaconda console from the root folder of the project + +```shell +python tests/azureml/src/luna_publish/azureml/publish_azureml_pipelines.py +``` + +You can specify the following parameters: + +- *--training_pipeline_name*: the name of training pipeline, default value is *mytrainingpipeline* +- *--batch_inference_pipeline_name*: the name of batch inference pipeline, default value is *mybatchinferencepipeline* +- *--deployment_pipeline_name*: the name of deployment pipeline, default value is *deployment_pipeline_name* +- *--aml_workspace_name*: the name of the AML workspace, if not specified, the one defined in the test_workspace.json will be used +- *--subscription_id*: the subscription id of the AML workspace, if not specified, the one defined in the test_workspace.json will be used +- *--resource_group*: the resource group name of the AML workspace, if not specified, the one defined in the test_workspace.json will be used + +It will publish all 3 pipelines to AML workspace: + +- training +- batch inference +- deployment + +Save the output. You will need to use it in the next step. + +## Next Step + +[Publish an AI service](./publish-ai-service.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-saas-offer.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-saas-offer.md new file mode 100644 index 0000000..4c6c29b --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/test-saas-offer.md @@ -0,0 +1,157 @@ +# Test the SaaS offer + +In this article, we will show you how to test the SaaS offer with and without a published Azure Marketplace SaaS offer. + +## Test with a published Azure Marketplace SaaS offer + +If you have your Azure Marketplace SaaS offer published in the preview steps (TODO: add link). You can subscribe the SaaS offer from Azure portal. + +Login into the [Azure Portal](https://portal.azure.com). Type SaaS in the search text box and choose "Software as a Service (SaaS)". + +![azure-portal-go-to-saas](../../images/luna.ai/azure-portal-go-to-saas.png) + +Click on the "Add" button on the upper left corner. It will bring you to the Azure Marketplace. + +![azure-portal-azure-marketplace](../../images/luna.ai/azure-portal-azure-marketplace.png) + +If you published offer with a public plan, you can search for your offer name in the search text box. If you published a private plan and whitelisted your organization's tenant id, click on the "View private offers" link on the "You have private offers available" banner to view all the private offers. + +Once you find the SaaS offer you published, click on the tile. It will open the offer details page. Select the private plan you created for this tutorial and click on the "Set up + subscribe" button. + +![azure-portal-saas-offer-page](../../images/luna.ai/azure-portal-saas-offer-page.png) + +On the next page, give the subscription a name, choose the Azure subscription, and click on "Subscribe" button. + +![azure-portal-saas-offer-subscribe](../../images/luna.ai/azure-portal-saas-offer-subscribe.png) + +The subscription operation usaully taks 20 seconds to a minute, after the subscription completed, click on the "Configure SaaS account on publish's site". It will bring you to the landing page which is deployed and configured as a part of Luna service. + +![azure-portal-saas-offer-completed](../../images/luna.ai/azure-portal-saas-offer-completed.png) + +On the landing page, you will see all 3 offer parameters we created [when configuring SaaS offer in Luna management portal](./publish-saas-offer.md#add-offer-parameters). + +![luna-landing-page](../../images/luna.ai/luna-landing-page.png) + +Choose the AI service you want to test, fill in rest of the fields and click on "Submit" button. It will bring you to the user subscription management page where you can see all your subscriptions. + +![luna-user-subscription-list](../../images/luna.ai/luna-user-subscription-list.png) + +In the backend, Luna service started a state machine running all the provisioning steps as you configured, including calling the webhook to subscribe the AI service. The state machine runs every minute to move to the next state. it will take 3 to 5 minutes to finish the provisioning. + +After the provisioning is completed (you need to refresh the page to see the changes), you should see a hyperlink on the subcription name. Click on the hyperlink, it will open a modal with the AI service base url and the subscription key. + +![luna-user-portal-subscription-details](../../images/luna.ai/luna-user-portal-subscription-details.png) + +Now you can use either the [Postman collection or the python notebook we used to test the AI service](./test-ai-service.md) to continue the test. + +## Test without a published Azure Marketplace SaaS offer + +Publishing a SaaS offer in Azure Marketplace requires some marketing and legal materials. We understand that could take long or collabaration with other department in your organization. Here we are going to show you how can you test the SaaS offer using REST API. + +### Get AAD token + +Before creating your subscription, you need to get an AAD token to be able to call the REST APIs + +#### Find the AAD application info + +- Login into Azure Portal, type in "AAD" in the search textbox and select "Azure Active Directory" +- In the menu on the left side, select "App Registration" +- Select "All applications", and in the search text box, type in "uniqueName-apiapp-aad" where uniqueName is the unique name you used when deploying Luna service. +- Select the filtered item. +- On the overview page, write down the "Application (client) ID" and the "Directory (tenant) ID" +- Select "Authentication" in the menu on the left side, scroll down to the bottom and select "Yes" for the "Treat application as a public client." option. Click on Save. + +#### Get the AAD token + +- Open a Windows PowerShell window with administrator permission +- Run *install-module -name MSAL.PS* to install the MSAL.PS module and accept all the terms. If it says you need to install the latest PowerShellGet, run *install-module -name PowerShellGet -Force* to update the PowerShellGet module first and restart the Windows PowerShell window +- Replace the uniqueName, clientId and tenantId in the following PowerShell script, and run it: + + ```powershell + $uniqueName = "uniqueName" + $clientId = "clientId" + $tenantId = "tenantId" + + $resourceUrl = "https://graph.microsoft.com/" + $redirectUri = "https://" + $uniqueName + "-isvapp.azurewebsites.net" + $scopes = "api://" + $clientId + "/user_impersonation" + + $accessToken = Get-MsalToken -ClientId $clientId ` + -RedirectUri $redirectUri ` + -TenantId $tenantId ` + -Scopes $scopes + + $accessToken.AccessToken + ``` + +- copy the token. Depending on the tool you are using to run the PowerShell script, you may get linebreakers when copying the token. If that's the case, remove all the linebreakers. The valid token is a string without linebreakers. + +### Create a subscirption using REST API + +First, we will need to generate a new GUID as subscription id. + +Then in Postman, create a new request: + +```http +PUT https://uniqueName-apipp.azurewebsites.net/api/subscriptions/generated-guid +``` + +where *uniqueName* is the unique name you used when deploying Luna service and *generated-guid* is subscription id. + +- Change the http method to PUT +- In the request URL textbox, enter "https://uniqueName-apipp.azurewebsites.net/api/subscriptions/generated-guid" +- On the "Body" tab, select "raw" and change the type to "JSON" in the dropdown list. +- Put the following JSON string into the request body text field: + + ```json + { + "SubscriptionId": "generated-guid", + "Name": "subscription-name", + "OfferName": "your-offer-name", + "PlanName": "your-plan-name", + "quantity": 1, + "Owner": "your-aad-id", + "BeneficiaryTenantId": "00000000-0000-0000-0000-000000000000", + "PurchaserTenantId": "00000000-0000-0000-0000-000000000000", + "InputParameters": [ + { + "Name":"servicetype", + "Type":"string", + "Value":"real-time prediction" + }, + { + "Name":"email", + "Type":"string", + "Value":"your-email" + }, + { + "Name":"productupdate", + "Type":"boolean", + "Value":"true" + }] + } + ``` + +- Update the subscription id, subscription name, offer name, plan name, owner, service type value, and your email in the request body. +- On the "Authorization" tab, select "Bearer Token" as type and add the token you got from the previous step. Make sure there's no linebreakers in the token. +- Submit the request. + +In the backend, Luna service started a state machine running all the provisioning steps as you configured, including calling the webhook to subscribe the AI service. The state machine runs every minute to move to the next state. it will take 3 to 5 minutes to finish the provisioning. You can use the following request to query the provisioning status: + +```http +GET https://uniqueName-apipp.azurewebsites.net/api/subscriptions/generated-guid +``` + +where *uniqueName* is the unique name you used when deploying Luna service and *generated-guid* is subscription id you generated in the previous step. Make sure you configured the same Bearer token on the "Authorization" tab before sending the request. + +Since the offer is not published in Azure Marketplace, the provisioning will fail at the last step when trying to notify Azure Marketplace to activate the subscription. It is expected. When you see the "provisioningStatus" of the GET request is "NotificationFailed", you can continue to run the following http request to get your base url and subscription key: + +```http +GET https://uniqueName-apipp.azurewebsites.net/api/apisubscriptions/generated-guid +``` + +Again, use the same Bearer token for authentication. You should see the baseUrl and keys in the response body. Now you can use either the [Postman collection or the python notebook we used to test the AI service](./test-ai-service.md) to continue the test. + +## Next Step + +[Config usage based billing](./config-meter-based-billing.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/use-luna-ml-project-template.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/use-luna-ml-project-template.md new file mode 100644 index 0000000..b73fdea --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/end-to-end-tutorial/use-luna-ml-project-template.md @@ -0,0 +1,29 @@ +# Create a ML project using Luna.AI project template + +In this article, we will show you how to create and config a ML project using Luna.AI project template + +## Create the project using Luna.AI project template + +You can find the Luna.AI project template [here](https://github.com/Azure/LunaAIProjectTemplate). + +### If you are creating a new repo using GitHub + +Simply click the "Use this template" button and create a new GitHub repo using the template + +### If you are using the template on an existing repo, using other Git product or not using any Git product + +Clone the repo or download the code by clicking the "Code" button and merge it into your Git repo. + +## Config the project before using it + +The Luna.AI project template is designed in the way that data scientists can write platform-free machine learning code and later run it in Azure Machine Learning Services or MLflow servers (not supported yet). Before using this template, you need to change a few configure values: + +- In *.cloud/.azureml/aml_run_config.yml*, update value of "target" to the name of compute cluster you created in your Azure Machine Learning service. If you are using the Azure Machine Learning service created when deploying Luna service, the default compute cluster name is *lunaamlcompute*. If you are using your own Azure Machine Learning workspace, see [here](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-set-up-training-targets#portal-create) for how to create a compute cluster. +- In *.cloud/.azureml/compute.yml*, update value of "deployment_target" to "aks" for deploying to Azure Kubernetes Service or "aci" to deploying to Azure Container Instance. If "aks" is specified, update value of "aks_cluster" to the name of AKS cluster you created in Azure Machine Learning service. In this tutorial, we will use Azure Container Instance. If you want to continue with AKS as the deployment target, see [here](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-deploy-azure-kubernetes-service#create-a-new-aks-cluster) about how to create and attach an AKS cluster in Azure Machine Learning workspace +- In *.cloud/.azureml/test_workspace.json", update value of "Scope" to the resource Id of your Azure Machine Learning service. You can find the resource Id in the "Settings/Properties" blade of the Azure Machine Learning service in Azure portal. + +![azure-portal-aml-resource-id](../../images/luna.ai/azure-portal-aml-resource-id.png) + +## Next Step + +[Add your code to the ML project](./add-ml-code.md) diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/how-to/README.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/how-to/README.md new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/how-to/how-to-find-azure-info.md b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/how-to/how-to-find-azure-info.md new file mode 100644 index 0000000..d2ea929 --- /dev/null +++ b/end-to-end-solutions/Luna/Resources/Documentation/luna.ai/how-to/how-to-find-azure-info.md @@ -0,0 +1,25 @@ +# How to find general Azure info + +In this article, we will show you how to find some general information + +## Where to find the tenant id + +In Azure Portal, type in "aad" in the search textbox and select "Azure Active Directory". You will find the tenant id on the "Azure Active Directory" page + +![azure-portal-tenant-id](../../images/luna.ai/azure-portal-how-to-find-tenant-id.png) + +## Where to find the subscription id + +In Azure Portal, type in "subscriptions" in the search textbox and select "Subscriptions". You will find the subscription id on this page + +![azure-portal-subscription-id](../../images/luna.ai/azure-portal-how-to-find-subscription-id.png) + +## Where to find your AAD user id + +In Azure Portal, type in "users" in the search textbox and select "Users". Then type in your name of your alias in the "Search user" textbox, you will find your AAD user id + +![azure-portal-user-id](../../images/luna.ai/azure-portal-how-to-find-user-id.png) + +## How to find a valid Azure region + +You can find all available Azure regions from [here](https://azure.microsoft.com/en-us/global-infrastructure/geographies/). diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/APIRouting/APIRoutingController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/APIRouting/APIRoutingController.cs new file mode 100644 index 0000000..dd6c3ce --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/APIRouting/APIRoutingController.cs @@ -0,0 +1,479 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Luna.Clients.Azure.APIM; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Controller; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Clients.Models.Controller; +using Luna.Services.Data; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Luna.API.Controllers.Admin +{ + /// + /// API controller for product resource. + /// + // [Authorize] + [ApiController] + [Consumes("application/json")] + [Produces("application/json")] + [Route("api")] + public class APIRoutingController : ControllerBase + { + private readonly IProductService _productService; + private readonly IDeploymentService _deploymentService; + private readonly IAPIVersionService _apiVersionService; + private readonly IAMLWorkspaceService _amlWorkspaceService; + private readonly IAPISubscriptionService _apiSubscriptionService; + private readonly ILogger _logger; + private readonly IUserAPIM _userAPIM; + private readonly IClientCertAPIM _certificateAPIM; + private readonly IKeyVaultHelper _keyVaultHelper; + private readonly IOptionsMonitor _options; + + /// + /// Constructor that uses dependency injection. + /// + /// The logger. + public APIRoutingController(IProductService productService, IDeploymentService deploymentService, IAPIVersionService apiVersionService, IAMLWorkspaceService amlWorkspaceService, IAPISubscriptionService apiSubscriptionService, + ILogger logger, + IUserAPIM userAPIM, IClientCertAPIM certificateAPIM, IOptionsMonitor options, IKeyVaultHelper keyVaultHelper) + { + _productService = productService ?? throw new ArgumentNullException(nameof(productService)); + _deploymentService = deploymentService ?? throw new ArgumentNullException(nameof(deploymentService)); + _apiVersionService = apiVersionService ?? throw new ArgumentNullException(nameof(apiVersionService)); + _amlWorkspaceService = amlWorkspaceService ?? throw new ArgumentNullException(nameof(amlWorkspaceService)); + _apiSubscriptionService = apiSubscriptionService ?? throw new ArgumentNullException(nameof(apiSubscriptionService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _userAPIM = userAPIM ?? throw new ArgumentNullException(nameof(userAPIM)); + _certificateAPIM = certificateAPIM ?? throw new ArgumentNullException(nameof(certificateAPIM)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _keyVaultHelper = keyVaultHelper; + } + + [HttpPost("products/{productName}/deployments/{deploymentName}/predict")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task Predict(string productName, string deploymentName, [FromQuery(Name = "api-version")] string versionName, [FromBody] PredictRequest request) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(request.subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (request.userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = new Luna.Data.Entities.AMLWorkspace(); + if (version.AMLWorkspaceName != null) + { + workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + } + + return this.Content((await ControllerHelper.Predict(version, workspace, request.userInput)), "application/json"); + } + + [HttpPost("products/{productName}/deployments/{deploymentName}/batchinference")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task BatchInferenceWithDefaultModel(string productName, string deploymentName, [FromQuery(Name = "api-version")] string versionName, [FromBody] BatchInferenceRequest request) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(request.subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (request.userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.BatchInferenceWithDefaultModel(product, deployment, version, workspace, apiSubcription, request.userInput)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/operations/{operationId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetABatchInferenceOperationWithDefaultModel(string productName, string deploymentName, Guid operationId, [FromQuery(Name = "api-version")] string versionName, [FromBody] BatchInferenceRequest request) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(request.subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (request.userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetABatchInferenceOperationWithDefaultModel(product, deployment, version, workspace, apiSubcription, operationId)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/operations")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task ListAllInferenceOperationsByUserWithDefaultModel(string productName, string deploymentName, [FromQuery(Name = "api-version")] string versionName, [FromBody] BatchInferenceRequest request) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(request.subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (request.userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.ListAllInferenceOperationsByUserWithDefaultModel(product, deployment, version, workspace, apiSubcription)); + } + + [HttpPost("products/{productName}/deployments/{deploymentName}/train")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task TrainModel(string productName, string deploymentName, [FromQuery(Name = "api-version")] string versionName, [FromBody] TrainModelRequest request) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(request.subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (request.userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.TrainModel(product, deployment, version, workspace, apiSubcription, request.userInput)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/operations/training/{modelId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetATrainingOperationsByModelIdUser(string productName, string deploymentName, Guid subscriptionId, Guid modelId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetATrainingOperationsByModelIdUser(product, deployment, version, workspace, apiSubcription, modelId)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/operations/training")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task ListAllTrainingOperationsByUser(string productName, string deploymentName, Guid subscriptionId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.ListAllTrainingOperationsByUser(product, deployment, version, workspace, apiSubcription)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/models/{modelId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAModelByModelIdUserProductDeployment(string productName, string deploymentName, Guid subscriptionId, Guid modelId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetAModelByModelIdUserProductDeployment(product, deployment, version, workspace, apiSubcription, modelId)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/models")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllModelsByUserProductDeployment(string productName, string deploymentName, Guid subscriptionId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetAllModelsByUserProductDeployment(product, deployment, version, workspace, apiSubcription)); + } + + [HttpDelete("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/models/{modelId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task DeleteAModel(string productName, string deploymentName, Guid subscriptionId, Guid modelId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + await ControllerHelper.DeleteAModel(workspace, modelId); + + return Ok(); + } + + [HttpPost("products/{productName}/deployments/{deploymentName}/models/{modelId}/batchinference")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task BatchInference(string productName, string deploymentName, string modelId, [FromQuery(Name = "api-version")] string versionName, [FromBody] BatchInferenceRequest request) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(request.subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (request.userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + return Ok(await ControllerHelper.BatchInference(product, deployment, version, workspace, apiSubcription, modelId, request.userInput)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/operations/inference/{operationId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetABatchInferenceOperation(string productName, string deploymentName, Guid subscriptionId, Guid operationId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetABatchInferenceOperation(product, deployment, version, workspace, apiSubcription, operationId)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/operations/inference")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task ListAllInferenceOperationsByUser(string productName, string deploymentName, Guid subscriptionId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.ListAllInferenceOperationsByUser(product, deployment, version, workspace, apiSubcription)); + } + + [HttpPost("products/{productName}/deployments/{deploymentName}/models/{modelId}/deploy")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task DeployRealTimePredictionEndpoint(string productName, string deploymentName, Guid modelId, [FromQuery(Name = "api-version")] string versionName, [FromBody] BatchInferenceRequest request) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(request.subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (request.userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.DeployRealTimePredictionEndpoint(product, deployment, version, workspace, apiSubcription, modelId, request.userInput)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/operations/deployment/{endpointId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetADeployOperationByEndpointIdUser(string productName, string deploymentName, Guid subscriptionId, Guid endpointId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetADeployOperationByEndpointIdUser(product, deployment, version, workspace, apiSubcription, endpointId)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/operations/deployment")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task ListAllDeployOperationsByUser(string productName, string deploymentName, Guid subscriptionId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.ListAllDeployOperationsByUser(product, deployment, version, workspace, apiSubcription)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/endpoints")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllRealTimeServiceEndpointsByUserProductAndDeployment(string productName, string deploymentName, Guid subscriptionId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetAllRealTimeServiceEndpointsByUserProductDeployment(product, deployment, version, workspace, apiSubcription)); + } + + [HttpGet("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/endpoints/{endpointId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetARealTimeServiceEndpointByEndpointIdUserProductAndDeployment(string productName, string deploymentName, Guid subscriptionId, Guid endpointId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var product = await _productService.GetAsync(productName); + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + return Ok(await ControllerHelper.GetARealTimeServiceEndpointByEndpointIdUserProductDeployment(product, deployment, version, workspace, apiSubcription, endpointId)); + } + + + [HttpDelete("products/{productName}/deployments/{deploymentName}/subscriptions/{subscriptionId}/endpoints/{endpointId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task DeleteAEndpoint(string productName, string deploymentName, Guid subscriptionId, Guid endpointId, [FromQuery(Name = "userid")] string userId, [FromQuery(Name = "api-version")] string versionName) + { + var apiSubcription = await _apiSubscriptionService.GetAsync(subscriptionId); + if (apiSubcription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubcription)), UserErrorCode.PayloadNotProvided); + } + if (userId != _userAPIM.GetUserName(apiSubcription.UserId)) + throw new LunaBadRequestUserException("UserId of request is not equal to apiSubscription.", UserErrorCode.InvalidParameter); + + var version = await _apiVersionService.GetAsync(productName, deploymentName, versionName); + var workspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + + + await ControllerHelper.DeleteAEndpoint(workspace, endpointId); + + return Ok(); + } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateController.cs index b279be9..782db8d 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading.Tasks; using Luna.Clients.Azure.Auth; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateParameterController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateParameterController.cs index c2baacb..faeb8c1 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateParameterController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/ArmTemplateParameterController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterController.cs index 1c076cd..e772140 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading.Tasks; using Luna.Clients.Azure.Auth; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterDimensionController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterDimensionController.cs index b6442d2..7cb19a5 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterDimensionController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/CustomMeterDimensionController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/IpConfigController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/IpConfigController.cs index 9ca06cf..2e76693 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/IpConfigController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/IpConfigController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/AMLWorkspaceController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/AMLWorkspaceController.cs new file mode 100644 index 0000000..2c92eca --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/AMLWorkspaceController.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Threading.Tasks; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Controller; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.Entities; +using Luna.Services.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Luna.API.Controllers.Admin +{ + /// + /// API controller for workspace resource. + /// + [ApiController] + [Authorize] + [Consumes("application/json")] + [Produces("application/json")] + [Route("api")] + public class AMLWorkspaceController : ControllerBase + { + private readonly IAMLWorkspaceService _workspaceService; + + private readonly ILogger _logger; + + /// + /// Constructor that uses dependency injection. + /// + /// The service to inject. + /// The logger. + public AMLWorkspaceController(IAMLWorkspaceService workspaceService, ILogger logger) + { + _workspaceService = workspaceService ?? throw new ArgumentNullException(nameof(workspaceService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Get deployed pipelines from a workspace. + /// + /// The name of the workspace to get. + /// HTTP 200 OK with workspace JSON object in response body. + [HttpGet("amlworkspaces/{workspaceName}/pipelines")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllPipelinesAsync(string workspaceName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Get workspace {workspaceName}"); + var workspace = await _workspaceService.GetAsync(workspaceName); + return Ok(await ControllerHelper.GetAllPipelines(workspace)); + } + + /// + /// Gets all workspaces. + /// + /// HTTP 200 OK with workspace JSON objects in response body. + [HttpGet("amlworkspaces")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllAsync() + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation("Get all workspaces."); + return Ok(await _workspaceService.GetAllAsync()); + } + + /// + /// Get an workspace. + /// + /// The name of the workspace to get. + /// HTTP 200 OK with workspace JSON object in response body. + [HttpGet("amlworkspaces/{workspaceName}", Name = nameof(GetAsync) + nameof(AMLWorkspace))] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAsync(string workspaceName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Get workspace {workspaceName}"); + return Ok(await _workspaceService.GetAsync(workspaceName)); + } + + /// + /// Creates or updates an workspace. + /// + /// The name of the workspace to update. + /// The updated workspace object. + /// HTTP 204 NO CONTENT. + [HttpPut("amlworkspaces/{workspaceName}")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task CreateOrUpdateAsync(string workspaceName, [FromBody] AMLWorkspace workspace) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + if (workspace == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(workspace)), UserErrorCode.PayloadNotProvided); + } + + if (!workspaceName.Equals(workspace.WorkspaceName)) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(AMLWorkspace).Name), + UserErrorCode.NameMismatch); + } + + if (await _workspaceService.ExistsAsync(workspaceName)) + { + _logger.LogInformation($"Update workspace {workspaceName} with payload {JsonConvert.SerializeObject(workspace)}"); + await _workspaceService.UpdateAsync(workspaceName, workspace); + return Ok(workspace); + } + else + { + _logger.LogInformation($"Create workspace {workspaceName} with payload {JsonConvert.SerializeObject(workspace)}"); + await _workspaceService.CreateAsync(workspace); + return CreatedAtRoute(nameof(GetAsync) + nameof(AMLWorkspace), new { workspaceName = workspace.WorkspaceName }, workspace); + } + } + + /// + /// Deletes an workspace. + /// + /// The name of the workspace to delete. + /// HTTP 204 NO CONTENT. + [HttpDelete("amlworkspaces/{workspaceName}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task DeleteAsync(string workspaceName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Delete workspace {workspaceName}."); + await _workspaceService.DeleteAsync(workspaceName); + return NoContent(); + } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/APISubscriptionController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/APISubscriptionController.cs new file mode 100644 index 0000000..3296d06 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/APISubscriptionController.cs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.DataContracts; +using Luna.Data.Entities; +using Luna.Data.Enums; +using Luna.Services.CustomMeterEvent; +using Luna.Services.Data; +using Luna.Services.Marketplace; +using Luna.Services.Provisoning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Luna.API.Controllers.Admin +{ + /// + /// API controller for the apiSubscription resource. + /// + [ApiController] + [Authorize] + [Consumes("application/json")] + [Produces("application/json")] + [Route("api")] + public class APISubscriptionController : ControllerBase + { + private readonly IAPISubscriptionService _apiSubscriptionService; + private readonly ILogger _logger; + + /// + /// .ctor + /// + /// The apiSubscription service instance + /// The logger. + public APISubscriptionController(IAPISubscriptionService apiSubscriptionService, + ILogger logger) + { + _apiSubscriptionService = apiSubscriptionService ?? throw new ArgumentNullException(nameof(apiSubscriptionService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Gets all active apiSubscriptions. + /// + /// HTTP 200 OK with apiSubscription JSON objects in response body. + [HttpGet("apiSubscriptions")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllAsync() + { + _logger.LogInformation(LoggingUtils.ComposeGetAllResourcesMessage(typeof(APISubscription).Name)); + + string owner = ""; + if (Request.Query.ContainsKey("owner")) + { + owner = Request.Query["owner"].ToString(); + _logger.LogInformation($"APISubscription owner name: {owner}."); + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, false, owner); + } + else + { + // user can only query their own apiSubscriptions + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + } + + string[] statusList = null; + if (Request.Query.ContainsKey("status")) + { + var status = Request.Query["status"].ToString(); + object statusEnum; + if (Enum.TryParse(typeof(FulfillmentState), status, true, out statusEnum)) + { + _logger.LogInformation($"Getting apiSubscriptions in {status} state."); + statusList = new string[] { status }; + } + else + { + _logger.LogInformation($"Getting active apiSubscriptions only"); + statusList = new string[] {nameof(FulfillmentState.PendingFulfillmentStart), + nameof(FulfillmentState.Subscribed), + nameof(FulfillmentState.Suspended)}; + } + } + + return Ok(await _apiSubscriptionService.GetAllAsync(status: statusList, owner: owner)); + } + + /// + /// Gets an apiSubscription. + /// + /// The apiSubscription id. + /// HTTP 200 OK with apiSubscription JSON object in response body. + [HttpGet("apiSubscriptions/{apiSubscriptionId}", Name = nameof(GetAsync) + nameof(APISubscription))] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAsync(Guid apiSubscriptionId) + { + _logger.LogInformation($"Get apiSubscription {apiSubscriptionId}."); + var apiSubscription = await _apiSubscriptionService.GetAsync(apiSubscriptionId); + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, false, apiSubscription.UserId); + return Ok(apiSubscription); + } + + [HttpPost("apiSubscriptions/CreateWithId")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task CreateAsync([FromQuery] string subscriptionName, + [FromQuery] Guid subscriptionId, + [FromQuery] string productName, + [FromQuery] string deploymentName, + [FromQuery] string userId) + { + APISubscription apiSubscription = new APISubscription() + { + SubscriptionName = subscriptionName, + SubscriptionId = subscriptionId, + ProductName = productName, + DeploymentName = deploymentName, + UserId = userId + }; + + return await CreateInternal(apiSubscription); + } + + /// The apiSubscription object + [HttpPost("apiSubscriptions/Create")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task CreateAsync([FromBody] APISubscription apiSubscription) + { + if (apiSubscription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubscription)), UserErrorCode.PayloadNotProvided); + } + + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, false, apiSubscription.UserId); + + return await CreateInternal(apiSubscription); + } + + private async Task CreateInternal(APISubscription apiSubscription) + { + _logger.LogInformation($"Create apiSubscription {apiSubscription.SubscriptionName} with payload {JsonSerializer.Serialize(apiSubscription)}."); + // Create a new apiSubscription + await _apiSubscriptionService.CreateAsync(apiSubscription); + return CreatedAtRoute(nameof(GetAsync) + nameof(APISubscription), new + { + apiSubscriptionId = apiSubscription.SubscriptionId + }, apiSubscription); + } + + /// + /// Update an apiSubscription + /// + /// The apiSubscription id. + /// The apiSubscription object + /// The apiSubscription info + [HttpPut("apiSubscriptions/{apiSubscriptionId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task UpdateAsync(Guid apiSubscriptionId, [FromBody] APISubscription apiSubscription) + { + if (apiSubscription == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiSubscription)), UserErrorCode.PayloadNotProvided); + } + + if (!apiSubscriptionId.Equals(apiSubscription.SubscriptionId)) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(APISubscription).Name), + UserErrorCode.NameMismatch); + } + _logger.LogInformation($"Update apiSubscription {apiSubscriptionId} with payload {JsonSerializer.Serialize(apiSubscription)}."); + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, false, apiSubscription.UserId); + + await _apiSubscriptionService.UpdateAsync(apiSubscriptionId, apiSubscription); + return Ok(await _apiSubscriptionService.GetAsync(apiSubscriptionId)); + + } + + /// + /// Deletes an apiSubscription. + /// + /// The apiSubscription id. + /// HTTP 204 NO CONTENT. + [HttpDelete("apiSubscriptions/{apiSubscriptionId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task DeleteAsync(Guid apiSubscriptionId) + { + var apiSubscription = await _apiSubscriptionService.GetAsync(apiSubscriptionId); + + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, false, apiSubscription.UserId); + + _logger.LogInformation($"Delete apiSubscription {apiSubscriptionId}."); + await _apiSubscriptionService.DeleteAsync(apiSubscriptionId); + return NoContent(); + } + + /// + /// Deletes an apiSubscriptions from query parameter. + /// + /// HTTP 204 NO CONTENT. + [HttpPost("apiSubscriptions/Delete")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [AllowAnonymous] + public async Task DeleteByPostAsync() + { + Guid apiSubscriptionId; + if (Request.Query.ContainsKey("SubscriptionId") && Guid.TryParse(Request.Query["SubscriptionId"].ToString(), out apiSubscriptionId)) + { + _logger.LogInformation($"Delete apiSubscription {apiSubscriptionId}."); + await _apiSubscriptionService.DeleteAsync(apiSubscriptionId); + return NoContent(); + } + else + { + throw new LunaBadRequestUserException("The query parameter SubscriptionId is not found.", UserErrorCode.InvalidParameter); + } + } + + /// + /// Regenerate key for an apiSubscription + /// + /// The apiSubscription id. + /// HTTP 200 OK with apiSubscription JSON objects in response body. + [HttpPost("apiSubscriptions/{apiSubscriptionId}/regenerateKey")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task RegenerateKey(Guid apiSubscriptionId, [FromBody] APISubscriptionKeyName keyName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Regenerate {keyName.KeyName} key for apiSubscription {apiSubscriptionId}."); + + if (!await _apiSubscriptionService.ExistsAsync(apiSubscriptionId)) + { + throw new LunaNotFoundUserException($"The specified apiSubscription {apiSubscriptionId} doesn't exist or you don't have permission to access it."); + } + + return Ok(await _apiSubscriptionService.RegenerateKey(apiSubscriptionId, keyName.KeyName)); + } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/APIVersionController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/APIVersionController.cs new file mode 100644 index 0000000..5875214 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/APIVersionController.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.DataContracts.Luna.AI; +using Luna.Data.Entities; +using Luna.Services.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Luna.API.Controllers.Admin +{ + /// + /// API controller for the apiVersion resource. + /// + [ApiController] + [Authorize] + [Consumes("application/json")] + [Produces("application/json")] + [Route("api")] + public class APIVersionController : ControllerBase + { + private readonly IAPIVersionService _apiVersionService; + private readonly ILogger _logger; + + /// + /// Constructor that uses dependency injection. + /// + /// The service to inject. + /// The logger. + public APIVersionController(IAPIVersionService apiVersionService, ILogger logger) + { + _apiVersionService = apiVersionService ?? throw new ArgumentNullException(nameof(apiVersionService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Get all apiVersions within a deployment within an product. + /// + /// The name of the product. + /// The name of the deployment. + /// HTTP 200 OK with apiVersions JSON objects in response body. + [HttpGet("products/{productName}/deployments/{deploymentName}/apiVersions")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllAsync(string productName, string deploymentName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Get all apiVersions in deployment {deploymentName} in product {productName}."); + return Ok(await _apiVersionService.GetAllAsync(productName, deploymentName)); + } + + /// + /// Get an apiVersion + /// + /// The name of the product. + /// The name of the deployment. + /// The name of apiversion + /// HTTP 200 OK with one apiVersion JSON objects in response body. + [HttpGet("products/{productName}/deployments/{deploymentName}/apiVersions/{versionName}", Name = nameof(GetAsync) + nameof(APIVersion))] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAsync(string productName, string deploymentName, string versionName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Get apiVersion {versionName.ToString()} in deployment {deploymentName} in product {productName}."); + return Ok(await _apiVersionService.GetAsync(productName, deploymentName, versionName)); + } + + /// + /// Creates pr update a apiVersion within a deployment within an product. + /// + /// The name of the product. + /// The name of the deployment. + /// The name of apiversion + /// The apiVersion object to create. + /// HTTP 201 CREATED with URI to created resource in response header. + /// HTTP 200 OK with updated apiVersion JSON objects in response body. + [HttpPut("products/{productName}/deployments/{deploymentName}/apiVersions/{versionName}")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task CreateOrUpdateAsync(string productName, string deploymentName, string versionName, [FromBody] APIVersion apiVersion) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + if (apiVersion == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(apiVersion)), UserErrorCode.PayloadNotProvided); + } + + if (!versionName.Equals(apiVersion.VersionName)) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(APIVersion).Name), + UserErrorCode.NameMismatch); + } + + if(await _apiVersionService.ExistsAsync(productName, deploymentName, versionName)) + { + _logger.LogInformation($"Update apiVersion {versionName} in deployment {deploymentName} in product {productName} with payload {JsonSerializer.Serialize(apiVersion)}."); + apiVersion = await _apiVersionService.UpdateAsync(productName, deploymentName, versionName, apiVersion); + return Ok(apiVersion); + } + else + { + _logger.LogInformation($"Create apiVersion {versionName} in deployment {deploymentName} in product {productName} with payload {JsonSerializer.Serialize(apiVersion)}."); + await _apiVersionService.CreateAsync(productName, deploymentName, apiVersion); + return CreatedAtRoute(nameof(GetAsync) + nameof(APIVersion), new { productName = productName, deploymentName = deploymentName, versionName = versionName }, apiVersion); + } + + } + + + /// + /// Delete an apiVersion + /// + /// The name of the product. + /// The name of the deployment. + /// The name of apiversion + /// HTTP 204 NO CONTENT + [HttpDelete("products/{productName}/deployments/{deploymentName}/apiVersions/{versionName}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task DeleteAsync(string productName, string deploymentName, string versionName) + { + + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Delete apiVersion {versionName.ToString()} in deployment {deploymentName} in product {productName}."); + await _apiVersionService.DeleteAsync(productName, deploymentName, versionName); + return NoContent(); + } + + /// + /// Get apiVersion source types + /// + /// The apiVersion + [HttpGet("apiVersions/sourceTypes")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetSourceTypes() + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + List list = new List(); + + list.Add(new APIVersionSourceType() { DisplayName = "Azure ML Pipelines", id = "amlPipeline" }); + list.Add(new APIVersionSourceType() { DisplayName = "Git repo", id = "git" }); + _logger.LogInformation($"Get apiVersion source types."); + return Ok(list); + } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/DeploymentController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/DeploymentController.cs new file mode 100644 index 0000000..435011f --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/DeploymentController.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.Entities; +using Luna.Services.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Luna.API.Controllers.Admin +{ + /// + /// API controller for deployment resource. + /// + [ApiController] + [Authorize] + [Consumes("application/json")] + [Produces("application/json")] + [Route("api")] + public class DeploymentController : ControllerBase + { + private readonly IDeploymentService _deploymentService; + private readonly ILogger _logger; + private readonly IAPIVersionService _apiVersionService; + + /// + /// Constructor that uses dependency injection. + /// + /// The service to inject. + /// The logger. + public DeploymentController(IDeploymentService deploymentService, ILogger logger, IAPIVersionService apiVersionService) + { + _deploymentService = deploymentService ?? throw new ArgumentNullException(nameof(deploymentService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _apiVersionService = apiVersionService ?? throw new ArgumentNullException(nameof(apiVersionService)); + } + + /// + /// Gets all deployments within an product. + /// + /// The name of the product. + /// HTTP 200 OK with deployment JSON objects in response body. + [HttpGet("products/{productName}/deployments")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllAsync(string productName) + { + // all users can call this API. + _logger.LogInformation($"Get all deployments in product {productName}."); + return Ok(await _deploymentService.GetAllAsync(productName)); + } + + /// + /// Gets a deployment within an product. + /// + /// The name of the product. + /// The name of the deployment to get. + /// HTTP 200 OK with deployment JSON object in response body. + [HttpGet("products/{productName}/deployments/{deploymentName}", Name = nameof(GetAsync) + nameof(Deployment))] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAsync(string productName, string deploymentName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Get deployment {deploymentName} in product {productName}."); + return Ok(await _deploymentService.GetAsync(productName, deploymentName)); + } + + /// + /// Create or update a deployment within an product. + /// + /// The name of the product. + /// The name of the deployment to update. + /// The updated deployment object. + /// HTTP 204 NO CONTENT. + [HttpPut("products/{productName}/deployments/{deploymentName}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task CreateOrUpdateAsync(string productName, string deploymentName, [FromBody] Deployment deployment) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + if (deployment == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(deployment)), UserErrorCode.PayloadNotProvided); + } + + if (!deploymentName.Equals(deployment.DeploymentName)) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(Deployment).Name), + UserErrorCode.NameMismatch); + } + + if (await _deploymentService.ExistsAsync(productName, deploymentName)) + { + _logger.LogInformation($"Update deployment {deploymentName} in product {productName} with payload {JsonSerializer.Serialize(deployment)}."); + deployment = await _deploymentService.UpdateAsync(productName, deploymentName, deployment); + return Ok(deployment); + } + else + { + _logger.LogInformation($"Create deployment {deploymentName} in product {productName} with payload {JsonSerializer.Serialize(deployment)}."); + await _deploymentService.CreateAsync(productName, deployment); + return CreatedAtRoute(nameof(GetAsync) + nameof(Deployment), new { productName = productName, deploymentName = deployment.DeploymentName }, deployment); + } + } + + /// + /// Deletes a deployment within an product. + /// + /// The name of the product. + /// The name of the deployment to delete. + /// HTTP 204 NO CONTENT. + [HttpDelete("products/{productName}/deployments/{deploymentName}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task DeleteAsync(string productName, string deploymentName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Delete deployment {deploymentName} from product {productName}."); + + // check if there exist apiversions + var apiVersions = await _apiVersionService.GetAllAsync(productName, deploymentName); + if (apiVersions.Count != 0) + { + throw new LunaConflictUserException($"Unable to delete {deploymentName} with subscription"); + } + + await _deploymentService.DeleteAsync(productName, deploymentName); + return NoContent(); + } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/ProductController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/ProductController.cs new file mode 100644 index 0000000..934cded --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/Luna.AI/ProductController.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.DataContracts.Luna.AI; +using Luna.Data.Entities; +using Luna.Services.Data; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Luna.API.Controllers.Admin +{ + /// + /// API controller for product resource. + /// + [ApiController] + [Authorize] + [Consumes("application/json")] + [Produces("application/json")] + [Route("api")] + public class ProductController : ControllerBase + { + private readonly IProductService _productService; + + private readonly ILogger _logger; + + /// + /// Constructor that uses dependency injection. + /// + /// The service to inject. + /// The logger. + public ProductController(IProductService productService, ILogger logger) + { + _productService = productService ?? throw new ArgumentNullException(nameof(productService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Gets all products. + /// + /// HTTP 200 OK with product JSON objects in response body. + [HttpGet("products")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAllAsync() + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation("Get all products."); + return Ok(await _productService.GetAllAsync()); + + } + + /// + /// Get a product. + /// + /// The name of the product to get. + /// HTTP 200 OK with product JSON object in response body. + [HttpGet("products/{productName}", Name = nameof(GetAsync) + nameof(Product))] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetAsync(string productName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Get product {productName}"); + return Ok(await _productService.GetAsync(productName)); + } + + [HttpGet("productTypes")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetProductTypes() + { + List productTypeList = new List(); + + productTypeList.Add(new ProductType() { DisplayName = "Real-time Prediction", Id = "RTP" }); + productTypeList.Add(new ProductType() { DisplayName = "Batch Inference", Id = "BI" }); + productTypeList.Add(new ProductType() { DisplayName = "Train Your Own Model", Id = "TYOM" }); + + return Ok(productTypeList); + + } + + [HttpGet("hostTypes")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task GetHostTypes() + { + List productTypeList = new List(); + + productTypeList.Add(new ProductType() { DisplayName = "SaaS", Id = "SaaS" }); + productTypeList.Add(new ProductType() { DisplayName = "Bring Your Own Compute", Id = "BYOC" }); + + return Ok(productTypeList); + + } + + /// + /// Creates or updates an product. + /// + /// The name of the product to update. + /// The updated product object. + /// HTTP 201 CREATED with URI to created resource in response header. + /// HTTP 200 OK with updated product JSON objects in response body. + [HttpPut("products/{productName}")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task CreateOrUpdateAsync(string productName, [FromBody] Product product) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + if (product == null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(product)), UserErrorCode.PayloadNotProvided); + } + + if (!productName.Equals(product.ProductName)) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(Product).Name), + UserErrorCode.NameMismatch); + } + + if (await _productService.ExistsAsync(productName)) + { + _logger.LogInformation($"Update product {productName} with payload {JsonConvert.SerializeObject(product)}"); + product = await _productService.UpdateAsync(productName, product); + return Ok(product); + } + else + { + _logger.LogInformation($"Create product {productName} with payload {JsonConvert.SerializeObject(product)}"); + await _productService.CreateAsync(product); + return CreatedAtRoute(nameof(GetAsync) + nameof(Product), new { productName = product.ProductName }, product); + } + } + + /// + /// Deletes an product. + /// + /// The name of the product to delete. + /// HTTP 204 NO CONTENT. + [HttpDelete("products/{productName}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task DeleteAsync(string productName) + { + AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true); + _logger.LogInformation($"Delete product {productName}."); + await _productService.DeleteAsync(productName); + return NoContent(); + } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferController.cs index 48312f9..dcc6404 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading.Tasks; using Luna.Clients.Azure.Auth; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferParameterController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferParameterController.cs index 6fa9afe..c0a4eb1 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferParameterController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/OfferParameterController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/PlanController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/PlanController.cs index 91ad46b..51101e9 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/PlanController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/PlanController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/RestrictedUserController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/RestrictedUserController.cs index a6c9a09..f4e99a0 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/RestrictedUserController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/RestrictedUserController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/SubscriptionController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/SubscriptionController.cs index ca01052..680bcaf 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/SubscriptionController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/SubscriptionController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/TelemetryDataConnectorController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/TelemetryDataConnectorController.cs index 70dcc3e..98f00b1 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/TelemetryDataConnectorController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/TelemetryDataConnectorController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookController.cs index eabd22f..a47590d 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookParameterController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookParameterController.cs index 515578d..a180392 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookParameterController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Admin/WebhookParameterController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Customer/WebHookController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Customer/WebHookController.cs index c47df5f..c1dfbdd 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Customer/WebHookController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Customer/WebHookController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Provisioning/ProvisioningController.cs b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Provisioning/ProvisioningController.cs index d1eaf98..555a836 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Controllers/Provisioning/ProvisioningController.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Controllers/Provisioning/ProvisioningController.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Luna.API.csproj b/end-to-end-solutions/Luna/src/Luna.API/Luna.API.csproj index 31e6514..5d0efe4 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Luna.API.csproj +++ b/end-to-end-solutions/Luna/src/Luna.API/Luna.API.csproj @@ -11,6 +11,7 @@ + @@ -38,6 +39,10 @@ + + + + true bin\Debug\netcoreapp3.0\Luna.API.xml diff --git a/end-to-end-solutions/Luna/src/Luna.API/Program.cs b/end-to-end-solutions/Luna/src/Luna.API/Program.cs index f6685b9..97e7645 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Program.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Azure.KeyVault; diff --git a/end-to-end-solutions/Luna/src/Luna.API/Startup.cs b/end-to-end-solutions/Luna/src/Luna.API/Startup.cs index d4d71d7..6d29a72 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/Startup.cs +++ b/end-to-end-solutions/Luna/src/Luna.API/Startup.cs @@ -1,9 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.IO; using System.Net; using System.Reflection; using Luna.API.Controllers.Admin; using Luna.Clients; +using Luna.Clients.Azure.APIM; using Luna.Clients.Azure.Auth; using Luna.Clients.Azure.Storage; using Luna.Clients.CustomMetering; @@ -15,12 +19,14 @@ using Luna.Services; using Luna.Services.CustomMeterEvent; using Luna.Services.Data; +using Luna.Services.Data.Luna.AI; using Luna.Services.Marketplace; using Luna.Services.Provisoning; using Luna.Services.Utilities; using Luna.Services.WebHook; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.AzureAD.UI; +using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.OpenIdConnect; @@ -37,8 +43,8 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Identity.Web.Resource; -using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; +using Newtonsoft.Json; using Polly; using Polly.Extensions.Http; @@ -136,6 +142,10 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddControllers().AddJsonOptions(options => { + options.JsonSerializerOptions.IgnoreNullValues = true; + }); + services.Configure( options => { @@ -148,7 +158,7 @@ public void ConfigureServices(IServiceCollection services) .AddAzureAD(options => this.configuration.Bind("AzureAd", options)); services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme) - .AddAzureADBearer(options => this.configuration.Bind("AzureAd", options)); + .AddAzureADBearer(options => this.configuration.Bind("AzureAd", options)); AADAuthHelper.AdminList = this.configuration["ISVPortal:AdminAccounts"].Split(';', StringSplitOptions.RemoveEmptyEntries); @@ -268,6 +278,21 @@ public void ConfigureServices(IServiceCollection services) // Register the db context interface services.TryAddScoped(); + services.AddOptions().Configure( + options => + { + this.configuration.Bind("SecuredCredentials:APIM", options); + }); + + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); services.AddOptions().Configure( options => { @@ -308,7 +333,13 @@ public void ConfigureServices(IServiceCollection services) services.AddHttpClient("Luna", x => { x.BaseAddress = new Uri(configuration.GetValue("LunaClient:BaseUri")); }); services.TryAddScoped(); - + // Register Luna.AI services + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped(); + services.AddCors(); services.AddRazorPages(); @@ -333,6 +364,11 @@ public void ConfigureServices(IServiceCollection services) o.Conventions.Controller().HasApiVersion(latest); o.Conventions.Controller().HasApiVersion(latest); o.Conventions.Controller().HasApiVersion(latest); + o.Conventions.Controller().HasApiVersion(latest); + o.Conventions.Controller().HasApiVersion(latest); + o.Conventions.Controller().HasApiVersion(latest); + o.Conventions.Controller().HasApiVersion(latest); + o.Conventions.Controller().HasApiVersion(latest); }); // Register the Swagger generator, defining 1 or more Swagger documents diff --git a/end-to-end-solutions/Luna/src/Luna.API/appsettings.json b/end-to-end-solutions/Luna/src/Luna.API/appsettings.json index 15664bb..5cd211f 100644 --- a/end-to-end-solutions/Luna/src/Luna.API/appsettings.json +++ b/end-to-end-solutions/Luna/src/Luna.API/appsettings.json @@ -1,30 +1,42 @@ { "SecuredCredentials": { - "VaultName": "", + "VaultName": "lunaaitest-keyvault", "StorageAccount": { "Config": { - "VaultName": "", - "AccountName": "", + "VaultName": "lunaaitest-keyvault", + "AccountName": "lunaaiteststorage", "AccountKey": "storage-key" } }, "Database": { - "DatabaseName": "", + "DatabaseName": "lunaaitest-sqldb", "ConnectionString": "connection-string" }, "ResourceManager": { "AzureActiveDirectory": { - "VaultName": "", - "ClientId": "", - "TenantId": "", + "VaultName": "lunaaitest-keyvault", + "ClientId": "68549edd-6ef3-4324-bd86-be757eeb15a5", + "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "AppKey": "arm-app-key" } }, + "APIM": { + "Config": { + "VaultName": "lunaaitest-keyvault", + "SubscriptionId": "a6c2a7cc-d67e-4a1a-b765-983f08c0423a", + "ResourceGroupName": "lunaaitest-rg", + "APIMServiceName": "lunav2", + "APIVersion": "2019-12-01", + "UId": "integration", + "Key": "apim-key", + "ControllerBaseUrl": "https://lunaaitest-apiapp.azurewebsites.net" + } + }, "Marketplace": { "AzureActiveDirectory": { - "VaultName": "", - "ClientId": "", - "TenantId": "", + "VaultName": "lunaaitest-keyvault", + "ClientId": "5d98b8e4-450b-48de-9c60-3136232c28b3", + "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "AppKey": "amp-app-key" } } @@ -33,7 +45,7 @@ "Instance": "https://sts.windows.net", "Domain": "lunamgmt.onmicrosoft.com", "TenantId": "common", - "ClientId": "", + "ClientId": "b9285d6f-f251-40c0-8123-7aa49ef61d0e", "CallbackPath": "/signin-oidc", "SignedOutCallbackPath ": "/signout-callback-oidc" }, @@ -55,8 +67,8 @@ "BaseUri": "https://lunamgmt.azurewebsites.net/api" }, "ISVPortal": { - "AdminTenant": "", - "AdminAccounts": "" + "AdminTenant": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "AdminAccounts": "" }, "Logging": { "LogLevel": { @@ -66,6 +78,6 @@ "ApiVersion": "2019-10-01", "AllowedHosts": "*", "ApplicationInsights": { - "InstrumentationKey": "" + "InstrumentationKey": "de5908f9-26d2-4556-9099-3518072d300c" } } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/APIMConfiguration.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/APIMConfiguration.cs new file mode 100644 index 0000000..bc90810 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/APIMConfiguration.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; + +namespace Luna.Clients.Azure.APIM +{ + public class APIMConfigurationOption + { + public APIMConfiguration Config { get; set; } + } + + public class APIMConfiguration + { + public Guid SubscriptionId { get; set; } + public string ResourceGroupname { get; set; } + public string APIMServiceName { get; set; } + public string APIVersion { get; set; } + public string VaultName { get; set; } + public string UId { get; set; } + public string Key { get; set; } + public string ControllerBaseUrl { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/APIMUtility.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/APIMUtility.cs new file mode 100644 index 0000000..a095438 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/APIMUtility.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using Luna.Clients.Azure.Storage; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Luna.Clients.Azure.APIM +{ + public class APIMUtility: IAPIMUtility + { + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private ILogger _logger; + private HttpClient _httpClient; + + [ActivatorUtilitiesConstructor] + public APIMUtility(IOptionsMonitor options, + ILogger logger, + HttpClient httpClient) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/IAPIMUtility.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/IAPIMUtility.cs new file mode 100644 index 0000000..f4696bd --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/IAPIMUtility.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Azure.APIM +{ + public interface IAPIMUtility + { + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/IClientCertAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/IClientCertAPIM.cs new file mode 100644 index 0000000..fa17885 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/IClientCertAPIM.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Luna.Clients.Azure.Auth; +using System.Threading.Tasks; + +namespace Luna.Clients.Azure.APIM +{ + public interface IClientCertAPIM + { + string GetAPIMRESTAPIPath(string owner); + Task GetCert(string owner); + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APISubscriptionAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APISubscriptionAPIM.cs new file mode 100644 index 0000000..6e91c92 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APISubscriptionAPIM.cs @@ -0,0 +1,253 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Luna.Clients.Azure.APIM +{ + public class APISubscriptionAPIM : IAPISubscriptionAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private const string BASE_URL_FORMAT = "https://{0}.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/subscriptions/{3}"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + private IProductAPIM _productAPIM; + private IUserAPIM _userAPIM; + + private string _baseUrl; + private string _requestBaseUrl; + + [ActivatorUtilitiesConstructor] + public APISubscriptionAPIM(IOptionsMonitor options, + HttpClient httpClient, + IProductAPIM productAPIM, + IUserAPIM userAPIM, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apiVersion = options.CurrentValue.Config.APIVersion; + _baseUrl = string.Format(BASE_URL_FORMAT, _apimServiceName); + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _productAPIM = productAPIM; + _userAPIM = userAPIM; + } + + private Uri GetSubscriptionAPIMRequestURI(Guid subscriptionId, string path = "") + { + var builder = new UriBuilder(_requestBaseUrl + GETAPIMRESTAPIPath(subscriptionId) + path); + + var query = HttpUtility.ParseQueryString(string.Empty); + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + private Models.Azure.APISubscription GetSubscription(APISubscription subscription) + { + Models.Azure.APISubscription subscriptionAPIM = new Models.Azure.APISubscription(); + subscriptionAPIM.name = subscription.SubscriptionId.ToString(); + subscriptionAPIM.properties.scope = _productAPIM.GetAPIMRESTAPIPath(subscription.ProductName); + subscriptionAPIM.properties.ownerId = _userAPIM.GetAPIMRESTAPIPath(subscription.UserId); + subscriptionAPIM.properties.state = Models.Azure.SubscriptionStatus.GetState(subscription.Status); + return subscriptionAPIM; + } + + public string GetBaseUrl(string productName, string deploymentName) + { + return string.Format("{0}/{1}/{2}", _baseUrl, productName, deploymentName); + } + + public string GETAPIMRESTAPIPath(Guid subscriptionId) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, subscriptionId.ToString()); + } + + public async Task ExistsAsync(APISubscription subscription) + { + Uri requestUri = GetSubscriptionAPIMRequestURI(subscription.SubscriptionId); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var body = JsonConvert.SerializeObject(GetSubscription(subscription)); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) return false; + + Models.Azure.APISubscription apiSubcriptionAPIM = (Models.Azure.APISubscription)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.APISubscription)); + if (apiSubcriptionAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(APISubscription subscription) + { + Uri requestUri = GetSubscriptionAPIMRequestURI(subscription.SubscriptionId); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var body = JsonConvert.SerializeObject(GetSubscription(subscription)); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + Models.Azure.APISubscription apiSubscription = (Models.Azure.APISubscription)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.APISubscription)); + if (apiSubscription == null || apiSubscription.properties == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Azure.APISubscription.Properties apiSubscriptionProperties = await ListSecrets(subscription.SubscriptionId); + apiSubscription.properties.primaryKey = apiSubscriptionProperties.primaryKey; + apiSubscription.properties.secondaryKey = apiSubscriptionProperties.secondaryKey; + + return apiSubscription; + } + + public async Task UpdateAsync(APISubscription subscription) + { + Uri requestUri = GetSubscriptionAPIMRequestURI(subscription.SubscriptionId); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var body = JsonConvert.SerializeObject(GetSubscription(subscription)); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + Models.Azure.APISubscription apiSubscription = (Models.Azure.APISubscription)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.APISubscription)); + if (apiSubscription == null || apiSubscription.properties == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Azure.APISubscription.Properties apiSubscriptionProperties = await ListSecrets(subscription.SubscriptionId); + apiSubscription.properties.primaryKey = apiSubscriptionProperties.primaryKey; + apiSubscription.properties.secondaryKey = apiSubscriptionProperties.secondaryKey; + + return apiSubscription; + } + + public async Task DeleteAsync(Data.Entities.APISubscription subscription) + { + if (!(await ExistsAsync(subscription))) return; + + Uri requestUri = GetSubscriptionAPIMRequestURI(subscription.SubscriptionId); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetSubscription(subscription)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + private async Task ListSecrets(Guid subscriptionId) + { + Uri requestUri = GetSubscriptionAPIMRequestURI(subscriptionId, "/listSecrets"); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + Models.Azure.APISubscription.Properties apiSubscriptionProperties = (Models.Azure.APISubscription.Properties)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.APISubscription.Properties)); + if (apiSubscriptionProperties == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + if (apiSubscriptionProperties.primaryKey == null || apiSubscriptionProperties.secondaryKey == null) + { + throw new LunaServerException($"Can't find any result. The response is {responseContent}."); + } + return apiSubscriptionProperties; + } + + public async Task RegenerateKey(Guid subscriptionId, string keyName) + { + Uri requestUri = GetSubscriptionAPIMRequestURI(subscriptionId, "/regenerate" + keyName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + Models.Azure.APISubscription.Properties apiSubscriptionProperties = await ListSecrets(subscriptionId); + + return apiSubscriptionProperties; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APIVersionAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APIVersionAPIM.cs new file mode 100644 index 0000000..bc38276 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APIVersionAPIM.cs @@ -0,0 +1,279 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Luna.Clients.Azure.APIM +{ + public class APIVersionAPIM : IAPIVersionAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/apis/{3}"; + private string APIM_PATH_FORMAT = "{0}/{1}"; + private string CONTROLLER_PATH_FORMAT = "/api/products/{0}/deployments/{1}"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + private IAPIVersionSetAPIM _apiVersionSetAPIM; + + private string _requestBaseUrl; + private string _controllerBaseUrl; + + [ActivatorUtilitiesConstructor] + public APIVersionAPIM(IOptionsMonitor options, + HttpClient httpClient, + IAPIVersionSetAPIM apiVersionSetAPIM, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apiVersion = options.CurrentValue.Config.APIVersion; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _controllerBaseUrl = options.CurrentValue.Config.ControllerBaseUrl; + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _apiVersionSetAPIM = apiVersionSetAPIM; + } + + private Uri GetAPIVersionAPIMRequestURI(string productName, string deploymentName, string versionName, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(productName, deploymentName, versionName)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + private Uri GetOriginAPIVersionAPIMRequestURI(string productName, string deploymentName, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetOriginAPIMRESTAPIPath(productName, deploymentName)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + private Models.Azure.APIVersion GetAPIVersion(APIVersion version) + { + Models.Azure.APIVersion api = new Models.Azure.APIVersion(); + api.name = version.ProductName + version.DeploymentName + version.GetVersionIdFormat(); + api.properties.displayName = version.ProductName + version.DeploymentName + version.GetVersionIdFormat(); + api.properties.apiVersion = version.VersionName; + + api.properties.serviceUrl = _controllerBaseUrl + GetControllerPath(version.ProductName, version.DeploymentName); + api.properties.path = GetAPIMPath(version.ProductName, version.DeploymentName); + api.properties.apiVersionSetId = _apiVersionSetAPIM.GetAPIMRESTAPIPath(version.ProductName, version.DeploymentName); + + return api; + } + + private Models.Azure.APIVersion GetOriginAPIVersion(Deployment deployment) + { + Models.Azure.APIVersion api = new Models.Azure.APIVersion(); + api.name = deployment.ProductName + deployment.DeploymentName; + api.properties.displayName = deployment.ProductName + deployment.DeploymentName; + api.properties.apiVersion = deployment.ProductName + deployment.DeploymentName; + + api.properties.serviceUrl = ""; + api.properties.path = deployment.ProductName + deployment.DeploymentName; + api.properties.apiVersionSetId = _apiVersionSetAPIM.GetAPIMRESTAPIPath(deployment.ProductName, deployment.DeploymentName); + + return api; + } + + public string GetAPIMPath(string productName, string deploymentName) + { + return string.Format(APIM_PATH_FORMAT, productName, deploymentName); + } + + public string GetControllerBaseUrl() + { + return _controllerBaseUrl; + } + + public string GetControllerPath(string productName, string deploymentName) + { + return string.Format(CONTROLLER_PATH_FORMAT, productName, deploymentName); + } + + public string GetOriginAPIMRESTAPIPath(string productName, string deploymentName) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, productName + deploymentName); + } + + public string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, productName + deploymentName + versionName); + } + + public async Task ExistsAsync(APIVersion version) + { + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersion(version)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) return false; + + Models.Azure.APIVersion apiVersionAPIM = (Models.Azure.APIVersion)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.APIVersion)); + if (apiVersionAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(APIVersion version) + { + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var body = JsonConvert.SerializeObject(GetAPIVersion(version)); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(APIVersion version) + { + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersion(version)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(APIVersion version) + { + if (!(await ExistsAsync(version))) return; + + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersion(version)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task CreateAsync(Deployment deployment) + { + Uri requestUri = GetOriginAPIVersionAPIMRequestURI(deployment.ProductName, deployment.DeploymentName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetOriginAPIVersion(deployment)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(Deployment deployment) + { + Uri requestUri = GetOriginAPIVersionAPIMRequestURI(deployment.ProductName, deployment.DeploymentName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetOriginAPIVersion(deployment)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(Deployment deployment) + { + Uri requestUri = GetOriginAPIVersionAPIMRequestURI(deployment.ProductName, deployment.DeploymentName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetOriginAPIVersion(deployment)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APIVersionSetAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APIVersionSetAPIM.cs new file mode 100644 index 0000000..40a7112 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/APIVersionSetAPIM.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Luna.Clients.Azure.APIM +{ + public class APIVersionSetAPIM : IAPIVersionSetAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/apiVersionSets/{3}"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + + private string _requestBaseUrl; + + [ActivatorUtilitiesConstructor] + public APIVersionSetAPIM(IOptionsMonitor options, + HttpClient httpClient, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apiVersion = options.CurrentValue.Config.APIVersion; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + private Uri GetDeploymentAPIMRequestURI(string productName, string deploymentName, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(productName, deploymentName)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + private Models.Azure.APIVersionSet GetAPIVersionSet(Deployment deployment) + { + var apiVersionSet = new Models.Azure.APIVersionSet(); + apiVersionSet.name = deployment.ProductName + deployment.DeploymentName; + apiVersionSet.properties.displayName = deployment.ProductName + deployment.DeploymentName; + return apiVersionSet; + } + + public string GetAPIMRESTAPIPath(string productName, string deploymentName) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, productName + deploymentName); + } + + public async Task ExistsAsync(Deployment deployment) + { + Uri requestUri = GetDeploymentAPIMRequestURI(deployment.ProductName, deployment.DeploymentName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersionSet(deployment)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) return false; + + Models.Azure.APIVersionSet apiVersionSetAPIM = (Models.Azure.APIVersionSet)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.APIVersionSet)); + if (apiVersionSetAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(Deployment deployment) + { + Uri requestUri = GetDeploymentAPIMRequestURI(deployment.ProductName, deployment.DeploymentName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersionSet(deployment)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(Deployment deployment) + { + Uri requestUri = GetDeploymentAPIMRequestURI(deployment.ProductName, deployment.DeploymentName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersionSet(deployment)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(Deployment deployment) + { + if (!(await ExistsAsync(deployment))) return; + + Uri requestUri = GetDeploymentAPIMRequestURI(deployment.ProductName, deployment.DeploymentName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersionSet(deployment)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/Auth/APIMAuthHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/Auth/APIMAuthHelper.cs new file mode 100644 index 0000000..ebb69d6 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/Auth/APIMAuthHelper.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; + +namespace Luna.Clients.Azure.APIM +{ + public class APIMAuthHelper + { + private string _id; + private string _key; + private DateTime expireTime; + public APIMAuthHelper(string id, string key) + { + _id = id; + _key = key; + expireTime = DateTime.Now; + } + public string GetSharedAccessToken() + { + var key = string.Format("{0}", _key); + if (expireTime.Subtract(DateTime.Now).TotalDays < 1) expireTime = DateTime.UtcNow.AddDays(30); + var expiry = expireTime; + using (var encoder = new HMACSHA512(Encoding.UTF8.GetBytes(key))) + { + var dataToSign = _id + "\n" + expiry.ToString("O", CultureInfo.InvariantCulture); + var hash = encoder.ComputeHash(Encoding.UTF8.GetBytes(dataToSign)); + var signature = Convert.ToBase64String(hash); + var encodedToken = string.Format("uid={0}&ex={1:o}&sn={2}", _id, expiry, signature); + return encodedToken; + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ClientCertAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ClientCertAPIM.cs new file mode 100644 index 0000000..11c8d81 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ClientCertAPIM.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.IO; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Web; +using Luna.Clients.Azure.Auth; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System.Net.Http.Headers; +using Newtonsoft.Json; +using Luna.Clients.Exceptions; +using System.Threading.Tasks; + +namespace Luna.Clients.Azure.APIM +{ + public class ClientCertAPIM : IClientCertAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/certificates/testCert"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _requestBaseUrl; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + private string _apiVersion; + + [ActivatorUtilitiesConstructor] + public ClientCertAPIM(IOptionsMonitor options, + HttpClient httpClient, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentException(nameof(httpClient)); + _apiVersion = options.CurrentValue.Config.APIVersion; + } + + + public string GetAPIMRESTAPIPath(string owner) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName); + } + + private Uri GetCertificateAPIMRequestURI(string owner, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(owner)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + builder.Query = queryString; + + return new Uri(builder.ToString()); + } + + + public async Task GetCert(string owner) + { + Uri requestUri = GetCertificateAPIMRequestURI(owner); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(owner), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + ClientCertConfiguration cert = JsonConvert.DeserializeObject(responseContent); + + return cert; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPISubscriptionAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPISubscriptionAPIM.cs new file mode 100644 index 0000000..e431601 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPISubscriptionAPIM.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IAPISubscriptionAPIM + { + string GETAPIMRESTAPIPath(Guid subscriptionId); + + string GetBaseUrl(string productName, string deploymentName); + Task CreateAsync(APISubscription subscription); + Task UpdateAsync(APISubscription subscription); + Task DeleteAsync(Data.Entities.APISubscription subscription); + Task RegenerateKey(Guid subscriptionId, string keyName); + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPIVersionAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPIVersionAPIM.cs new file mode 100644 index 0000000..d62c88d --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPIVersionAPIM.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IAPIVersionAPIM + { + public string GetAPIMPath(string productName, string deploymentName); + public string GetControllerBaseUrl(); + public string GetControllerPath(string productName, string deploymentName); + public string GetOriginAPIMRESTAPIPath(string productName, string deploymentName); + public string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName); + Task ExistsAsync(APIVersion version); + Task CreateAsync(APIVersion version); + Task UpdateAsync(APIVersion version); + Task DeleteAsync(APIVersion version); + Task CreateAsync(Deployment deployment); + Task UpdateAsync(Deployment deployment); + Task DeleteAsync(Deployment deployment); + + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPIVersionSetAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPIVersionSetAPIM.cs new file mode 100644 index 0000000..a7b0e3e --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IAPIVersionSetAPIM.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IAPIVersionSetAPIM + { + string GetAPIMRESTAPIPath(string productName, string deploymentName); + Task CreateAsync(Deployment deployment); + Task UpdateAsync(Deployment deployment); + Task DeleteAsync(Deployment deployment); + + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IOperationAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IOperationAPIM.cs new file mode 100644 index 0000000..c71d8f6 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IOperationAPIM.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IOperationAPIM + { + public string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName, string operationName); + public Models.Azure.Operation GetOperation(Models.Azure.OperationTypeEnum operationType); + public Task ExistsAsync(APIVersion version, Models.Azure.Operation operation); + public Task CreateAsync(APIVersion version, Models.Azure.Operation operation); + public Task UpdateAsync(APIVersion version, Models.Azure.Operation operation); + public Task DeleteAsync(APIVersion version, Models.Azure.Operation operation); + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IPolicyAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IPolicyAPIM.cs new file mode 100644 index 0000000..f36734d --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IPolicyAPIM.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IPolicyAPIM + { + public string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName, string operationName); + + public Task ExistsAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType); + + public Task CreateAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType); + + public Task UpdateAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType); + + public Task DeleteAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType); + + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IProductAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IProductAPIM.cs new file mode 100644 index 0000000..f0fa645 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IProductAPIM.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IProductAPIM + { + string GetAPIMRESTAPIPath(string productName); + Task CreateAsync(Product product); + Task UpdateAsync(Product product); + Task DeleteAsync(Product product); + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IProductAPIVersionAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IProductAPIVersionAPIM.cs new file mode 100644 index 0000000..5a4bad8 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IProductAPIVersionAPIM.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IProductAPIVersionAPIM + { + string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName); + Task ExistsAsync(APIVersion version); + Task CreateAsync(APIVersion version); + Task UpdateAsync(APIVersion version); + Task DeleteAsync(APIVersion version); + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IUserAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IUserAPIM.cs new file mode 100644 index 0000000..dc80a0c --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/IUserAPIM.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Luna.Data.Entities; + +namespace Luna.Clients.Azure.APIM +{ + public interface IUserAPIM + { + string GetUserName(string owner); + string GetAPIMRESTAPIPath(string owner); + Task CreateAsync(string owner); + Task UpdateAsync(string owner); + Task DeleteAsync(string owner); + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/OperationAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/OperationAPIM.cs new file mode 100644 index 0000000..c345aff --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/OperationAPIM.cs @@ -0,0 +1,468 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using static Luna.Clients.Models.Azure.Operation; + +namespace Luna.Clients.Azure.APIM +{ + public class OperationAPIM : IOperationAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/apis/{3}/operations/{4}"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + + private string _requestBaseUrl; + + [ActivatorUtilitiesConstructor] + public OperationAPIM(IOptionsMonitor options, + HttpClient httpClient, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apiVersion = options.CurrentValue.Config.APIVersion; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + private Models.Azure.Operation RealTimePrediction() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "real-time-prediction"; + operation.properties.displayName = "real-time-prediction"; + operation.properties.method = "POST"; + operation.properties.urlTemplate = "/predict"; + + return operation; + } + + private Models.Azure.Operation BatchInferenceWithDefaultModel() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "batch-inference-with-default-model"; + operation.properties.displayName = "batch-inference-with-default-model"; + operation.properties.method = "POST"; + operation.properties.urlTemplate = "/batchinference"; + + return operation; + } + + private Models.Azure.Operation GetABatchInferenceOperationWithDefaultModel() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-a-batch-inference-operation-with-default-model"; + operation.properties.displayName = "get-a-batch-inference-operation-with-default-model"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations/{operationId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "operationId" } + }); + + return operation; + } + + private Models.Azure.Operation ListAllInferenceOperationsByUserWithDefaultModel() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "list-all-batch-inference-operations-by-user-with-default-model"; + operation.properties.displayName = "list-all-batch-inference-operations-by-user-with-default-model"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations"; + + return operation; + } + + private Models.Azure.Operation TrainModel() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "train-model"; + operation.properties.displayName = "train-model"; + operation.properties.method = "POST"; + operation.properties.urlTemplate = "/train"; + + return operation; + } + + private Models.Azure.Operation ListAllTrainingOperationsByUser() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "list-all-training-operations-by-user"; + operation.properties.displayName = "list-all-training-operations-by-user"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations/training"; + + return operation; + } + + private Models.Azure.Operation GetATrainingOperationsByModelIdUser() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-a-training-operation-by-modelid-user"; + operation.properties.displayName = "get-a-training-operation-by-modelid-and-user"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations/training/{modelId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "modelId" } + }); + + return operation; + } + + private Models.Azure.Operation GetAModelByModelIdUserProductDeployment() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-a-model-by-modelid-product-deployment"; + operation.properties.displayName = "get-a-model-by-modelid-product-deployment"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/models/{modelId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "modelId" } + }); + + return operation; + } + + private Models.Azure.Operation GetAllModelsByUserProductDeployment() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-all-models-by-user-product-deployment"; + operation.properties.displayName = "get-all-models-by-user-product-deployment"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/models"; + + return operation; + } + + private Models.Azure.Operation DeleteAModel() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "delete-a-model"; + operation.properties.displayName = "delete-a-model"; + operation.properties.method = "DELETE"; + operation.properties.urlTemplate = "/models/{modelId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "modelId" } + }); + + return operation; + } + + private Models.Azure.Operation BatchInference() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "batch-inference"; + operation.properties.displayName = "batch-inference"; + operation.properties.method = "POST"; + operation.properties.urlTemplate = "/models/{modelId}/batchinference"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "modelId" } + }); + + return operation; + } + + private Models.Azure.Operation GetABatchInferenceOperation() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-a-batch-inference-operation"; + operation.properties.displayName = "get-a-batch-inference-operation"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations/inference/{operationId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "operationId" } + }); + + return operation; + } + + private Models.Azure.Operation ListAllInferenceOperationsByUser() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "list-all-batch-inference-operations-by-user"; + operation.properties.displayName = "list-all-batch-inference-operations-by-user"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations/inference"; + + return operation; + } + + private Models.Azure.Operation DeployRealTimePredictionEndpoint() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "deploy-real-time-prediction-endpoint"; + operation.properties.displayName = "deploy-real-time-prediction-endpoint"; + operation.properties.method = "POST"; + operation.properties.urlTemplate = "/models/{modelId}/deploy"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "modelId" } + }); + + return operation; + } + + private Models.Azure.Operation GetADeployOperationByEndpointIdUser() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-a-deploy-operation-by-endpointid-user"; + operation.properties.displayName = "get-a-deploy-operation-by-endpointid-user"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations/deployment/{endpointId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "endpointId" } + }); + + return operation; + } + + private Models.Azure.Operation ListAllDeployOperationsByUser() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "list-all-deploy-operations-by-user"; + operation.properties.displayName = "list-all-deploy-operations-by-user"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/operations/deployment"; + + return operation; + } + + private Models.Azure.Operation GetAllRealTimeServiceEndpointsByUserProductDeployment() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-all-real-time-service-endpoints-by-user-product-deployment"; + operation.properties.displayName = "get-all-real-time-service-endpoints-by-user-product-deployment"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/endpoints"; + + return operation; + } + + private Models.Azure.Operation GetARealTimeServiceEndpointByEndpointIdUserProductDeployment() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "get-a-real-time-service-endpoint-by-endpointid-user-product-deployment"; + operation.properties.displayName = "get-a-real-time-service-endpoint-by-endpointid-user-product-deployment"; + operation.properties.method = "GET"; + operation.properties.urlTemplate = "/endpoints/{endpointId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "endpointId" } + }); + + return operation; + } + + private Models.Azure.Operation DeleteAEndpoint() + { + Models.Azure.Operation operation = new Models.Azure.Operation(); + + operation.name = "delete-a-endpoint"; + operation.properties.displayName = "delete-a-endpoint"; + operation.properties.method = "DELETE"; + operation.properties.urlTemplate = "/endpoints/{endpointId}"; + operation.properties.templateParameters = new List(new templateParameter[] + { + new templateParameter(){ name = "endpointId" } + }); + + return operation; + } + + private Uri GetAPIVersionAPIMRequestURI(string productName, string deploymentName, string versionName, string operationName, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(productName, deploymentName, versionName, operationName)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + public string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName, string operationName) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, productName + deploymentName + versionName, operationName); + } + + public Models.Azure.Operation GetOperation(Models.Azure.OperationTypeEnum operationType) + { + switch (operationType) + { + case Models.Azure.OperationTypeEnum.RealTimePrediction: + return RealTimePrediction(); + case Models.Azure.OperationTypeEnum.BatchInferenceWithDefaultModel: + return BatchInferenceWithDefaultModel(); + case Models.Azure.OperationTypeEnum.GetABatchInferenceOperationWithDefaultModel: + return GetABatchInferenceOperationWithDefaultModel(); + case Models.Azure.OperationTypeEnum.ListAllInferenceOperationsByUserWithDefaultModel: + return ListAllInferenceOperationsByUserWithDefaultModel(); + case Models.Azure.OperationTypeEnum.TrainModel: + return TrainModel(); + case Models.Azure.OperationTypeEnum.ListAllTrainingOperationsByUser: + return ListAllTrainingOperationsByUser(); + case Models.Azure.OperationTypeEnum.GetATrainingOperationsByModelIdUser: + return GetATrainingOperationsByModelIdUser(); + case Models.Azure.OperationTypeEnum.GetAModelByModelIdUserProductDeployment: + return GetAModelByModelIdUserProductDeployment(); + case Models.Azure.OperationTypeEnum.GetAllModelsByUserProductDeployment: + return GetAllModelsByUserProductDeployment(); + case Models.Azure.OperationTypeEnum.DeleteAModel: + return DeleteAModel(); + case Models.Azure.OperationTypeEnum.BatchInference: + return BatchInference(); + case Models.Azure.OperationTypeEnum.GetABatchInferenceOperation: + return GetABatchInferenceOperation(); + case Models.Azure.OperationTypeEnum.ListAllInferenceOperationsByUser: + return ListAllInferenceOperationsByUser(); + case Models.Azure.OperationTypeEnum.DeployRealTimePredictionEndpoint: + return DeployRealTimePredictionEndpoint(); + case Models.Azure.OperationTypeEnum.GetADeployOperationByEndpointIdUser: + return GetADeployOperationByEndpointIdUser(); + case Models.Azure.OperationTypeEnum.ListAllDeployOperationsByUser: + return ListAllDeployOperationsByUser(); + case Models.Azure.OperationTypeEnum.GetAllRealTimeServiceEndpointsByUserProductDeployment: + return GetAllRealTimeServiceEndpointsByUserProductDeployment(); + case Models.Azure.OperationTypeEnum.GetARealTimeServiceEndpointByEndpointIdUserProductDeployment: + return GetARealTimeServiceEndpointByEndpointIdUserProductDeployment(); + case Models.Azure.OperationTypeEnum.DeleteAEndpoint: + return DeleteAEndpoint(); + default: + throw new LunaServerException($"Invalid operation type. The type is {nameof(operationType)}."); + } + } + + public async Task ExistsAsync(APIVersion version, Models.Azure.Operation operation) + { + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operation.name); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(operation), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) return false; + + Models.Azure.Operation operationAPIM = (Models.Azure.Operation)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.Operation)); + if (operationAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(APIVersion version, Models.Azure.Operation operation) + { + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operation.name); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var body = JsonConvert.SerializeObject(operation); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(APIVersion version, Models.Azure.Operation operation) + { + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operation.name); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(operation), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(APIVersion version, Models.Azure.Operation operation) + { + if (!(await ExistsAsync(version, operation))) return; + + Uri requestUri = GetAPIVersionAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operation.name); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(operation), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/PolicyAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/PolicyAPIM.cs new file mode 100644 index 0000000..74815a5 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/PolicyAPIM.cs @@ -0,0 +1,797 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Luna.Clients.Azure.APIM +{ + public class PolicyAPIM : IPolicyAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/apis/{3}/operations/{4}/policies/policy"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + + private string _requestBaseUrl; + private string _controllerBaseUrl; + + [ActivatorUtilitiesConstructor] + public PolicyAPIM(IOptionsMonitor options, + HttpClient httpClient, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apiVersion = options.CurrentValue.Config.APIVersion; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _controllerBaseUrl = options.CurrentValue.Config.ControllerBaseUrl; + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + private Uri GetPolicyAPIMRequestURI(string productName, string deploymentName, string versionName, string operationName, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(productName, deploymentName, versionName, operationName)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + private Models.Azure.Policy RealTimePrediction(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName); + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + " + + @"@{ + var body = context.Request.Body.As<JObject>(); + var newBody = new JObject(); + newBody.Add(""userInput"", body.ToString().Replace(""\r\n"", """")); + newBody.Add(""subscriptionId"", context.Subscription.Id); + newBody.Add(""userId"", context.User.Id); + return newBody.ToString(); + }" + + @" + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy BatchInferenceWithDefaultModel(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName); + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + " + + @"@{ + var body = context.Request.Body.As<JObject>(); + var newBody = new JObject(); + newBody.Add(""userInput"", body.ToString().Replace(""\r\n"", """")); + newBody.Add(""subscriptionId"", context.Subscription.Id); + newBody.Add(""userId"", context.User.Id); + return newBody.ToString(); + }" + + @" + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetABatchInferenceOperationWithDefaultModel(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy ListAllInferenceOperationsByUserWithDefaultModel(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy TrainModel(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName); + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + " + + @"@{ + var body = context.Request.Body.As<JObject>(); + var newBody = new JObject(); + newBody.Add(""userInput"", body.ToString().Replace(""\r\n"", """")); + newBody.Add(""subscriptionId"", context.Subscription.Id); + newBody.Add(""userId"", context.User.Id); + return newBody.ToString(); + }" + + @" + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy ListAllTrainingOperationsByUser(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetATrainingOperationsByModelIdUser(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetAModelByModelIdUserProductDeployment(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetAllModelsByUserProductDeployment(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy DeleteAModel(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy BatchInference(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName); + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + " + + @"@{ + var body = context.Request.Body.As<JObject>(); + var newBody = new JObject(); + newBody.Add(""userInput"", body.ToString().Replace(""\r\n"", """")); + newBody.Add(""subscriptionId"", context.Subscription.Id); + newBody.Add(""userId"", context.User.Id); + return newBody.ToString(); + }" + + @" + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetABatchInferenceOperation(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy ListAllInferenceOperationsByUser(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy DeployRealTimePredictionEndpoint(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName); + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + " + + @"@{ + var body = context.Request.Body.As<JObject>(); + var newBody = new JObject(); + newBody.Add(""userInput"", body.ToString().Replace(""\r\n"", """")); + newBody.Add(""subscriptionId"", context.Subscription.Id); + newBody.Add(""userId"", context.User.Id); + return newBody.ToString(); + }" + + @" + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetAllDeployOperationsByEndpointIdUser(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy ListAllDeployOperationsByUser(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetAllRealTimeServiceEndpointsByUserProductDeployment(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetARealTimeServiceEndpointByEndpointIdUserProductDeployment(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy DeleteAEndpoint(APIVersion version) + { + Models.Azure.Policy policy = new Models.Azure.Policy(); + string backendUrl = _controllerBaseUrl + string.Format("/api/products/{0}/deployments/{1}", version.ProductName, version.DeploymentName) + "/subscriptions/{0}"; + policy.properties.value = + @" + + + " + + $"" + + @" + application/json + + + @(context.User.Id) + + + + + + + + + + + + "; + return policy; + } + + private Models.Azure.Policy GetPolicy(APIVersion version, Models.Azure.OperationTypeEnum operationType) + { + switch (operationType) + { + case Models.Azure.OperationTypeEnum.RealTimePrediction: + return RealTimePrediction(version); + case Models.Azure.OperationTypeEnum.BatchInferenceWithDefaultModel: + return BatchInferenceWithDefaultModel(version); + case Models.Azure.OperationTypeEnum.GetABatchInferenceOperationWithDefaultModel: + return GetABatchInferenceOperationWithDefaultModel(version); + case Models.Azure.OperationTypeEnum.ListAllInferenceOperationsByUserWithDefaultModel: + return ListAllInferenceOperationsByUserWithDefaultModel(version); + case Models.Azure.OperationTypeEnum.TrainModel: + return TrainModel(version); + case Models.Azure.OperationTypeEnum.ListAllTrainingOperationsByUser: + return ListAllTrainingOperationsByUser(version); + case Models.Azure.OperationTypeEnum.GetATrainingOperationsByModelIdUser: + return GetATrainingOperationsByModelIdUser(version); + case Models.Azure.OperationTypeEnum.GetAModelByModelIdUserProductDeployment: + return GetAModelByModelIdUserProductDeployment(version); + case Models.Azure.OperationTypeEnum.GetAllModelsByUserProductDeployment: + return GetAllModelsByUserProductDeployment(version); + case Models.Azure.OperationTypeEnum.DeleteAModel: + return DeleteAModel(version); + case Models.Azure.OperationTypeEnum.BatchInference: + return BatchInference(version); + case Models.Azure.OperationTypeEnum.GetABatchInferenceOperation: + return GetABatchInferenceOperation(version); + case Models.Azure.OperationTypeEnum.ListAllInferenceOperationsByUser: + return ListAllInferenceOperationsByUser(version); + case Models.Azure.OperationTypeEnum.DeployRealTimePredictionEndpoint: + return DeployRealTimePredictionEndpoint(version); + case Models.Azure.OperationTypeEnum.GetADeployOperationByEndpointIdUser: + return GetAllDeployOperationsByEndpointIdUser(version); + case Models.Azure.OperationTypeEnum.ListAllDeployOperationsByUser: + return ListAllDeployOperationsByUser(version); + case Models.Azure.OperationTypeEnum.GetAllRealTimeServiceEndpointsByUserProductDeployment: + return GetAllRealTimeServiceEndpointsByUserProductDeployment(version); + case Models.Azure.OperationTypeEnum.GetARealTimeServiceEndpointByEndpointIdUserProductDeployment: + return GetARealTimeServiceEndpointByEndpointIdUserProductDeployment(version); + case Models.Azure.OperationTypeEnum.DeleteAEndpoint: + return DeleteAEndpoint(version); + default: + throw new LunaServerException($"Invalid operation type. The type is {nameof(operationType)}."); + } + } + + public string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName, string operationName) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, productName + deploymentName + versionName, operationName); + } + + public async Task ExistsAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType) + { + Uri requestUri = GetPolicyAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operationName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetPolicy(version, operationType)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) return false; + + Models.Azure.Operation operationAPIM = (Models.Azure.Operation)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.Operation)); + if (operationAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType) + { + Uri requestUri = GetPolicyAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operationName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + var body = JsonConvert.SerializeObject(GetPolicy(version, operationType)); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType) + { + Uri requestUri = GetPolicyAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operationName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetPolicy(version, operationType)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(APIVersion version, string operationName, Models.Azure.OperationTypeEnum operationType) + { + if (!(await ExistsAsync(version, operationName, operationType))) return; + + Uri requestUri = GetPolicyAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat(), operationName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetPolicy(version, operationType)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ProductAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ProductAPIM.cs new file mode 100644 index 0000000..d43112e --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ProductAPIM.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Luna.Clients.Azure.APIM +{ + public class ProductAPIM : IProductAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/products/{3}"; + private static IDictionary DELETE_QUERY_PARAMS = new Dictionary + { + {"deleteSubscriptions","true"} + }; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + + private string _requestBaseUrl; + + [ActivatorUtilitiesConstructor] + public ProductAPIM(IOptionsMonitor options, + HttpClient httpClient, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _apiVersion = options.CurrentValue.Config.APIVersion; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + private Uri GetProductAPIMRequestURI(string productName, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(productName)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + private Models.Azure.Product GetProduct(Product product) + { + Models.Azure.Product apiProduct = new Models.Azure.Product(); + apiProduct.name = product.ProductName; + apiProduct.properties.displayName = product.ProductName; + return apiProduct; + } + + public string GetAPIMRESTAPIPath(string productName) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, productName); + } + + public async Task ExistsAsync(Product product) + { + Uri requestUri = GetProductAPIMRequestURI(product.ProductName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetProduct(product)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) return false; + + Models.Azure.Product productAPIM = (Models.Azure.Product)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.Product)); + if (productAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(Product product) + { + Uri requestUri = GetProductAPIMRequestURI(product.ProductName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetProduct(product)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(Product product) + { + Uri requestUri = GetProductAPIMRequestURI(product.ProductName); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetProduct(product)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(Product product) + { + if (!(await ExistsAsync(product))) return; + + Uri requestUri = GetProductAPIMRequestURI(product.ProductName, DELETE_QUERY_PARAMS); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetProduct(product)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ProductAPIVersionAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ProductAPIVersionAPIM.cs new file mode 100644 index 0000000..02cf981 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/ProductAPIVersionAPIM.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Controller; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Luna.Clients.Azure.APIM +{ + public class ProductAPIVersionAPIM : IProductAPIVersionAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/products/{3}/apis/{4}"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private HttpClient _httpClient; + private APIMAuthHelper _apimAuthHelper; + private IAPIVersionAPIM _apiVersionAPIM; + private IAPIVersionSetAPIM _apiVersionSetAPIM; + + private string _requestBaseUrl; + private string _controllerBaseUrl; + + [ActivatorUtilitiesConstructor] + public ProductAPIVersionAPIM(IOptionsMonitor options, + HttpClient httpClient, + IAPIVersionAPIM apiVersionAPIM, + IAPIVersionSetAPIM apiVersionSetAPIM, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apiVersion = options.CurrentValue.Config.APIVersion; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _apiVersionAPIM = apiVersionAPIM; + _apiVersionSetAPIM = apiVersionSetAPIM; + } + + private Uri GetProductAPIMRequestURI(string productName, string deploymentName, string versionName, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(productName, deploymentName, versionName)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = query.ToString(); + + return new Uri(builder.ToString()); + } + + private Models.Azure.APIVersion GetAPIVersion(APIVersion version) + { + Models.Azure.APIVersion versionAPIM = new Models.Azure.APIVersion(); + versionAPIM.name = version.GetVersionIdFormat(); + versionAPIM.properties.displayName = version.VersionName; + versionAPIM.properties.apiVersion = version.VersionName; + + versionAPIM.properties.serviceUrl = _apiVersionAPIM.GetControllerBaseUrl() + _apiVersionAPIM.GetControllerPath(version.ProductName, version.DeploymentName); + versionAPIM.properties.path = _apiVersionAPIM.GetAPIMPath(version.ProductName, version.DeploymentName); + versionAPIM.properties.apiVersionSetId = _apiVersionSetAPIM.GetAPIMRESTAPIPath(version.ProductName, version.DeploymentName); + + return versionAPIM; + } + + public string GetAPIMRESTAPIPath(string productName, string deploymentName, string versionName) + { + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, productName, productName + deploymentName + versionName); + } + + public async Task ExistsAsync(APIVersion version) + { + Uri requestUri = GetProductAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersion(version)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) return false; + + Models.Azure.APIVersion productAPIVersionAPIM = (Models.Azure.APIVersion)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.APIVersion)); + if (productAPIVersionAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(APIVersion version) + { + Uri requestUri = GetProductAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersion(version)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(APIVersion version) + { + Uri requestUri = GetProductAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersion(version)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(APIVersion version) + { + if (!(await ExistsAsync(version))) return; + + Uri requestUri = GetProductAPIMRequestURI(version.ProductName, version.DeploymentName, version.GetVersionIdFormat()); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetAPIVersion(version)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/UserAPIM.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/UserAPIM.cs new file mode 100644 index 0000000..466cade --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/APIM/Luna.AI/UserAPIM.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Luna.Clients.Azure.Auth; +using Luna.Clients.Exceptions; +using Luna.Data.Entities; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Luna.Clients.Azure.APIM +{ + public class UserAPIM : IUserAPIM + { + private const string REQUEST_BASE_URL_FORMAT = "https://{0}.management.azure-api.net"; + private string PATH_FORMAT = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.ApiManagement/service/{2}/users/{3}"; + private Guid _subscriptionId; + private string _resourceGroupName; + private string _apimServiceName; + private string _apiVersion; + private APIMAuthHelper _apimAuthHelper; + private HttpClient _httpClient; + + private string _requestBaseUrl; + + [ActivatorUtilitiesConstructor] + public UserAPIM(IOptionsMonitor options, + HttpClient httpClient, + IKeyVaultHelper keyVaultHelper) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + _subscriptionId = options.CurrentValue.Config.SubscriptionId; + _resourceGroupName = options.CurrentValue.Config.ResourceGroupname; + _apimServiceName = options.CurrentValue.Config.APIMServiceName; + _apiVersion = options.CurrentValue.Config.APIVersion; + _requestBaseUrl = string.Format(REQUEST_BASE_URL_FORMAT, _apimServiceName); + _apimAuthHelper = new APIMAuthHelper(options.CurrentValue.Config.UId, keyVaultHelper.GetSecretAsync(options.CurrentValue.Config.VaultName, options.CurrentValue.Config.Key).Result); + _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + } + + public string GetUserName(string owner) + { + return owner.Replace("@", "").Replace(".", ""); + } + + private Uri GetUserAPIMRequestURI(string owner, IDictionary queryParams = null) + { + var builder = new UriBuilder(_requestBaseUrl + GetAPIMRESTAPIPath(owner)); + + var query = HttpUtility.ParseQueryString(string.Empty); + foreach (KeyValuePair kv in queryParams ?? new Dictionary()) query[kv.Key] = kv.Value; + query["api-version"] = _apiVersion; + string queryString = query.ToString(); + + builder.Query = queryString; + + return new Uri(builder.ToString()); + } + + private Models.Azure.User GetUser(string owner) + { + string[] names = owner.Split('@'); + if (names.Length != 2) throw new InvalidOperationException($"user email format is invalid. email: {owner}"); + + Models.Azure.User user = new Models.Azure.User(); + user.name = GetUserName(owner); + user.properties.email = owner; + user.properties.firstName = names[0] ?? user.properties.firstName; + user.properties.lastName = names[1] ?? user.properties.lastName; + + return user; + } + + public string GetAPIMRESTAPIPath(string owner) + { + string userName = GetUserName(owner); + return string.Format(PATH_FORMAT, _subscriptionId, _resourceGroupName, _apimServiceName, userName); + } + + public async Task ExistsAsync(string owner) + { + Uri requestUri = GetUserAPIMRequestURI(owner); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetUser(owner)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + Models.Azure.User userAPIM = (Models.Azure.User)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Azure.User)); + if (userAPIM == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + return true; + } + + public async Task CreateAsync(string owner) + { + Uri requestUri = GetUserAPIMRequestURI(owner); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetUser(owner)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task UpdateAsync(string owner) + { + Uri requestUri = GetUserAPIMRequestURI(owner); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Put }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetUser(owner)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + + public async Task DeleteAsync(string owner) + { + if (!(await ExistsAsync(owner))) return; + + Uri requestUri = GetUserAPIMRequestURI(owner); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + request.Headers.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", _apimAuthHelper.GetSharedAccessToken()); + request.Headers.Add("If-Match", "*"); + + request.Content = new StringContent(JsonConvert.SerializeObject(GetUser(owner)), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/ARMTemplateHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/ARMTemplateHelper.cs index c2f5c41..32c34fd 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/ARMTemplateHelper.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/ARMTemplateHelper.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using Newtonsoft.Json; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AADAuthHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AADAuthHelper.cs index 1953af0..6ef17f4 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AADAuthHelper.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AADAuthHelper.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AuthenticationConfiguration.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AuthenticationConfiguration.cs index 05c5bf6..79282cc 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AuthenticationConfiguration.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/AuthenticationConfiguration.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; namespace Luna.Clients.Azure.Auth diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/ClientCertAuthHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/ClientCertAuthHelper.cs new file mode 100644 index 0000000..7168b01 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/ClientCertAuthHelper.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Security.Cryptography.X509Certificates; +using Luna.Clients.Azure.APIM; + + +namespace Luna.Clients.Azure.Auth +{ + public class ClientCertAuthHelper + { + public static bool IsValidClientCertificate(X509Certificate2 certificate, ClientCertConfiguration APIMCertificate) + { + //1. Check time validity of certificate + if (DateTime.Compare(DateTime.Now, certificate.NotBefore) < 0 || DateTime.Compare(DateTime.Now, certificate.NotAfter) > 0) + { + return false; + } + + //2. Check subject name of certificate + bool foundSubject = false; + string[] certSubjectData = certificate.Subject.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string s in certSubjectData) + { + if (String.Compare(s.Trim(), APIMCertificate.Properties.Subject) == 0) + { + foundSubject = true; + break; + } + } + if (!foundSubject) return false; + + //3. Check issuer name of certificate + bool foundIssuerCN = false; + string[] certIssuerData = certificate.Issuer.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string s in certIssuerData) + { + if (String.Compare(s.Trim(), "CN=Root Agency") == 0) + { + foundIssuerCN = true; + break; + } + } + if (!foundIssuerCN) ;// return false; + + // 4. Check thumbprint of certificate + if (String.Compare(certificate.Thumbprint.Trim().ToUpper(), APIMCertificate.Properties.Thumbprint) != 0) return false; + + return true; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/ClientCertConfiguration.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/ClientCertConfiguration.cs new file mode 100644 index 0000000..5733f5b --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/ClientCertConfiguration.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Azure.Auth +{ + public class ClientCertConfiguration + { + public string Id { get; set; } + public string Type { get; set; } + public string Name { get; set; } + public CertProp Properties { get; set; } + } + + public class CertProp + { + public string Subject { get; set; } + public string Thumbprint { get; set; } + public string ExpirationDate { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/IKeyVaultHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/IKeyVaultHelper.cs index 7a32de8..b028cb8 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/IKeyVaultHelper.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/IKeyVaultHelper.cs @@ -1,12 +1,17 @@ -using System.Threading.Tasks; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Threading.Tasks; using Microsoft.Azure.KeyVault.WebKey; namespace Luna.Clients.Azure.Auth { public interface IKeyVaultHelper - { + { + Task SetSecretAsync(string vaultName, string secretName, string value); Task GetSecretAsync(string vaultName, string secretName); - + Task DeleteSecretAsync(string vaultName, string secretName); + Task GetKeyAsync(string vaultName, string keyName); /// @@ -18,4 +23,4 @@ public interface IKeyVaultHelper /// Task GetBearerToken(AuthenticationConfiguration options, string resource); } -} \ No newline at end of file +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/KeyVaultHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/KeyVaultHelper.cs index 69a1ef9..969603d 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/KeyVaultHelper.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Auth/KeyVaultHelper.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading.Tasks; using Microsoft.Azure.KeyVault; @@ -25,6 +28,21 @@ public KeyVaultHelper(ILogger logger) ); } + public async Task SetSecretAsync(string vaultName, string secretName, string value) + { + try + { + _logger.LogInformation("SetSecretAsync to Key Vault"); + var secret = await keyVaultClient.SetSecretAsync($"https://{vaultName}.vault.azure.net/", secretName, value); + _logger.LogInformation("SetSecretAsync operation completed"); + return secret.Value; + } + catch(Exception ex) + { + throw new InvalidOperationException(ex.Message); + } + } + public async Task GetSecretAsync(string vaultName, string secretName) { try { @@ -37,6 +55,21 @@ public async Task GetSecretAsync(string vaultName, string secretName) } } + public async Task DeleteSecretAsync(string vaultName, string secretName) + { + try + { + _logger.LogInformation("DeleteSecretAsync from Key Vault"); + var secret = await keyVaultClient.DeleteSecretAsync($"https://{vaultName}.vault.azure.net/", secretName); + _logger.LogInformation("DeleteSecretAsync operation completed"); + return secret.Value; + } + catch (Exception ex) + { + throw new InvalidOperationException(ex.Message); + } + } + public async Task GetKeyAsync(string vaultName, string keyName) { try { diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/IStorageUtility.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/IStorageUtility.cs index e803e7f..9af0b86 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/IStorageUtility.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/IStorageUtility.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading.Tasks; using Microsoft.WindowsAzure.Storage.Table; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageAccountConfiguration.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageAccountConfiguration.cs index 05bad7d..27f804e 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageAccountConfiguration.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageAccountConfiguration.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; namespace Luna.Clients.Azure.Storage diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageUtility.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageUtility.cs index 03dea2d..492ba21 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageUtility.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Azure/Storage/StorageUtility.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading.Tasks; using Luna.Clients.Azure.Auth; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Controller/Auth/ControllerAuthHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Controller/Auth/ControllerAuthHelper.cs new file mode 100644 index 0000000..fb258bd --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Controller/Auth/ControllerAuthHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Clients.ActiveDirectory; + +namespace Luna.Clients.Controller.Auth +{ + public class ControllerAuthHelper + { + public async static Task GetToken(string tenantId, string clientId, string clientSecret) + { + string resourceUri = "https://management.azure.com/"; + string authorityUri = String.Format("https://login.microsoftonline.com/{0}", tenantId); + + AuthenticationContext authenticationContext = new AuthenticationContext(authorityUri, new TokenCache()); + ClientCredential clientCredential = new ClientCredential(clientId, clientSecret); + + return (await authenticationContext.AcquireTokenAsync(resourceUri, clientCredential)).AccessToken; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Controller/ControllerHelper.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Controller/ControllerHelper.cs new file mode 100644 index 0000000..82becea --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Controller/ControllerHelper.cs @@ -0,0 +1,929 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Luna.Clients.Controller.Auth; +using Luna.Clients.Exceptions; +using Luna.Clients.Models.Controller; +using Luna.Data.DataContracts.Luna.AI; +using Luna.Data.Entities; +using Newtonsoft.Json; + +namespace Luna.Clients.Controller +{ + public static class ControllerHelper + { + private static HttpClient HttpClient = new HttpClient(); + + public static string GetLunaGeneratedUuid() + { + return "a" + Guid.NewGuid().ToString("N").Substring(1); + } + + public static async Task GetRegion(AMLWorkspace workspace) + { + var requestUri = new Uri("https://management.azure.com" + workspace.ResourceId + "?api-version=2019-05-01"); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary workspaceDetails = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + return workspaceDetails["location"].ToString(); + } + + public static async Task> GetAllPipelines(AMLWorkspace workspace) + { + var requestUrl = $"https://{workspace.Region}.api.azureml.ms/pipelines/v1.0" + workspace.ResourceId + $"/pipelines"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + + List> rawPipelineList = (List>)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(List>)); + List pipelineList = new List(); + foreach (var item in rawPipelineList) + { + string displayName = item.ContainsKey("Name") && item["Name"] != null ? item["Name"].ToString() : "noName"; + string id = item.ContainsKey("Id") && item["Id"] != null ? item["Id"].ToString() : "noId"; + string description = item.ContainsKey("Description") && item["Description"] != null ? item["Description"].ToString() : "noDescription"; + string createdDate = item.ContainsKey("CreatedDate") && item["CreatedDate"] != null ? item["CreatedDate"].ToString() : "noCreatedDate"; + pipelineList.Add(new AMLPipeline() + { + DisplayName = displayName, + Id = id, + Description = description, + CreatedDate = createdDate + }); + } + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + return pipelineList; + } + + public static async Task Predict(APIVersion version, AMLWorkspace workspace, object body) + { + var requestUri = new Uri(version.RealTimePredictAPI); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + switch (version.AuthenticationType) + { + case "Token": + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + break; + case "Key": + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", version.AuthenticationKey); + break; + case "None": + break; + } + + request.Content = new StringContent(body.ToString()); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + return responseContent; + } + + public static async Task BatchInferenceWithDefaultModel(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, string userInput) + { + var requestUri = new Uri(version.BatchInferenceAPI); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var batchInferenceId = GetLunaGeneratedUuid(); + var batchInferenceWithDefaultModelRequest = new Models.Controller.Backend.BatchInferenceWithDefaultModelRequest(); + batchInferenceWithDefaultModelRequest.experimentName = $"p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_infer"; + batchInferenceWithDefaultModelRequest.parameterAssignments.userInput = userInput; + batchInferenceWithDefaultModelRequest.parameterAssignments.operationId = batchInferenceId; + batchInferenceWithDefaultModelRequest.tags.userId = apiSubscription.UserId; + batchInferenceWithDefaultModelRequest.tags.productName = product.ProductName; + batchInferenceWithDefaultModelRequest.tags.deploymentName = deployment.DeploymentName; + batchInferenceWithDefaultModelRequest.tags.apiVersion = version.VersionName; + batchInferenceWithDefaultModelRequest.tags.operationId = batchInferenceId; + batchInferenceWithDefaultModelRequest.tags.operationType = "inference"; + batchInferenceWithDefaultModelRequest.tags.subscriptionId = apiSubscription.SubscriptionId.ToString(); + + request.Content = new StringContent(JsonConvert.SerializeObject(batchInferenceWithDefaultModelRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + return new BatchInferenceResponse { operationId = batchInferenceId }; + } + + public static async Task GetABatchInferenceOperationWithDefaultModel(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid operationId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_infer/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var getABatchInferenceOperationRequest = new Models.Controller.Backend.GetABatchInferenceOperationRequest(); + getABatchInferenceOperationRequest.filter = $"runType eq azureml.PipelineRun and tags/operationType eq inference and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId} and tags/operationId eq {operationId.ToString("N")}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(getABatchInferenceOperationRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List operations = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (operations == null || operations.Count == 0) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + var operation = operations[0]; + Models.Controller.GetABatchInferenceOperationResponse getABatchInferenceOperationResponse = new Models.Controller.GetABatchInferenceOperationResponse() + { + operationId = operation.tags.operationId, + status = operation.status, + startTimeUtc = operation.startTimeUtc, + completeTimeUtc = operation.endTimeUtc, + error = operation.error, + }; + return getABatchInferenceOperationResponse; + } + + public static async Task ListAllInferenceOperationsByUserWithDefaultModel(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_infer/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var listAllInferenceOperationsByUserRequest = new Models.Controller.Backend.ListAllInferenceOperationsByUserRequest(); + listAllInferenceOperationsByUserRequest.filter = $"runType eq azureml.PipelineRun and tags/operationType eq inference and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(listAllInferenceOperationsByUserRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List operations = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (operations == null || operations.Count == 0) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.ListAllInferenceOperationsByUserResponse listAllInferenceOperationsByUserResponse = new Models.Controller.ListAllInferenceOperationsByUserResponse(); + foreach (var operation in operations) + { + listAllInferenceOperationsByUserResponse.operations.Add(new Models.Controller.ListAllInferenceOperationsByUserResponse.Operation() + { + operationId = operation.tags.operationId, + status = operation.status, + startTimeUtc = operation.startTimeUtc, + completeTimeUtc = operation.endTimeUtc, + error = operation.error, + }); + } + return listAllInferenceOperationsByUserResponse; + } + + public static async Task TrainModel(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, string userInput) + { + var requestUri = new Uri(version.TrainModelAPI); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var modelId = GetLunaGeneratedUuid(); + var trainModelRequest = new Models.Controller.Backend.TrainModelRequest(); + trainModelRequest.experimentName = $"p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_train"; + trainModelRequest.parameterAssignments.userInput = userInput; + trainModelRequest.parameterAssignments.modelId = modelId; + trainModelRequest.parameterAssignments.userId = apiSubscription.UserId; + trainModelRequest.parameterAssignments.productName = product.ProductName; + trainModelRequest.parameterAssignments.deploymentName = deployment.DeploymentName; + trainModelRequest.parameterAssignments.apiVersion = version.VersionName; + trainModelRequest.parameterAssignments.subscriptionId = apiSubscription.SubscriptionId.ToString(); + trainModelRequest.tags.userId = apiSubscription.UserId; + trainModelRequest.tags.productName = product.ProductName; + trainModelRequest.tags.deploymentName = deployment.DeploymentName; + trainModelRequest.tags.apiVersion = version.VersionName; + trainModelRequest.tags.modelId = modelId; + trainModelRequest.tags.operationId = modelId; + trainModelRequest.tags.operationType = "training"; + trainModelRequest.tags.subscriptionId = apiSubscription.SubscriptionId.ToString(); + + var body = JsonConvert.SerializeObject(trainModelRequest); + request.Content = new StringContent(body); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + return new TrainModelResponse { modelId = modelId }; + } + + public static async Task ListAllTrainingOperationsByUser(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_train/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var listAllTrainingOperationsByUserRequest = new Models.Controller.Backend.ListAllTrainingOperationsByUserRequest(); + listAllTrainingOperationsByUserRequest.filter = $"tags/operationType eq training and runType eq azureml.PipelineRun and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(listAllTrainingOperationsByUserRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List operations = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (operations == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.ListAllTrainingOperationsByUserResponse listAllTrainingOperationsByUserResponse = new Models.Controller.ListAllTrainingOperationsByUserResponse(); + foreach (var operation in operations) + { + listAllTrainingOperationsByUserResponse.operations.Add(new Models.Controller.ListAllTrainingOperationsByUserResponse.Operation() { + operationType = operation.tags.operationType, + modelId = operation.tags.modelId, + status = operation.status, + startTimeUtc = operation.startTimeUtc, + completeTimeUtc = operation.endTimeUtc, + description = operation.description, + error = operation.error + }); + } + return listAllTrainingOperationsByUserResponse; + } + + public static async Task GetATrainingOperationsByModelIdUser(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid modelId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_train/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var getAllTrainingOperationsByModelIdUserRequest = new Models.Controller.Backend.GetATrainingOperationsByModelIdUserRequest(); + getAllTrainingOperationsByModelIdUserRequest.filter = $"tags/operationType eq training and runType eq azureml.PipelineRun and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId} and tags/modelId eq {modelId.ToString("N")}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(getAllTrainingOperationsByModelIdUserRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List operations = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (operations == null || operations.Count == 0) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.GetATrainingOperationsByModelIdUserResponse getATrainingOperationsByModelIdUserResponse = new Models.Controller.GetATrainingOperationsByModelIdUserResponse() + { + operationType = operations[0].tags.operationType, + modelId = operations[0].tags.modelId, + status = operations[0].status, + startTimeUtc = operations[0].startTimeUtc, + completeTimeUtc = operations[0].endTimeUtc, + description = operations[0].description, + error = operations[0].error + }; + + return getATrainingOperationsByModelIdUserResponse; + } + + public static async Task GetAModelByModelIdUserProductDeployment(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid modelId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/models?tags=userId={apiSubscription.UserId},productName={product.ProductName},deploymentName={deployment.DeploymentName},subscriptionId={apiSubscription.SubscriptionId}&name={modelId.ToString("N")}"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List models = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (models == null || models.Count == 0) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.GetAModelByModelIdUserProductDeploymentResponse getAModelByModelIdUserProductDeploymentResponse = new Models.Controller.GetAModelByModelIdUserProductDeploymentResponse() + { + modelId = models[0].name, + startTimeUtc = models[0].createdTime, + completeTimeUtc = models[0].modifiedTime, + description = models[0].description, + }; + return getAModelByModelIdUserProductDeploymentResponse; + } + + public static async Task GetAllModelsByUserProductDeployment(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/models?tags=userId={apiSubscription.UserId},productName={product.ProductName},deploymentName={deployment.DeploymentName},subscriptionId={apiSubscription.SubscriptionId}"; + //var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/models?tags=userId=xiwu@microsoft.com,productName=eddi,deploymentName=westus,subscriptionId=a6c2a7cc-d67e-4a1a-b765-983f08c0423a"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List operations = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (operations == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.GetAllModelsByUserProductDeploymentResponse results = new Models.Controller.GetAllModelsByUserProductDeploymentResponse(); + foreach (var operation in operations) + { + results.models.Add(new Models.Controller.GetAllModelsByUserProductDeploymentResponse.Model() + { + modelId = operation.name, + startTimeUtc = operation.createdTime, + completeTimeUtc = operation.modifiedTime, + description = operation.description, + }); + } + return results; + } + + public static async Task DeleteAModel(AMLWorkspace workspace, Guid modelId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/models/{modelId.ToString("N")}"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + public static async Task BatchInference(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, string modelId, string userInput) + { + var requestUri = new Uri(version.BatchInferenceAPI); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var operationId = GetLunaGeneratedUuid(); + var batchInferenceRequest = new Models.Controller.Backend.BatchInferenceRequest(); + batchInferenceRequest.experimentName = $"p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_infer"; + batchInferenceRequest.parameterAssignments.userInput = userInput; + batchInferenceRequest.parameterAssignments.modelId = modelId; + batchInferenceRequest.parameterAssignments.operationId = operationId; + batchInferenceRequest.tags.userId = apiSubscription.UserId; + batchInferenceRequest.tags.productName = product.ProductName; + batchInferenceRequest.tags.deploymentName = deployment.DeploymentName; + batchInferenceRequest.tags.apiVersion = version.VersionName; + batchInferenceRequest.tags.modelId = modelId; + batchInferenceRequest.tags.operationId = operationId; + batchInferenceRequest.tags.operationType = "inference"; + batchInferenceRequest.tags.subscriptionId = apiSubscription.SubscriptionId.ToString(); + + request.Content = new StringContent(JsonConvert.SerializeObject(batchInferenceRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + return new BatchInferenceResponse { operationId = operationId }; + } + + public static async Task GetABatchInferenceOperation(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid operationId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_infer/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var getABatchInferenceOperationRequest = new Models.Controller.Backend.GetABatchInferenceOperationRequest(); + getABatchInferenceOperationRequest.filter = $"runType eq azureml.PipelineRun and tags/operationType eq inference and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId} and tags/operationId eq {operationId.ToString("N")}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(getABatchInferenceOperationRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List operations = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (operations == null || operations.Count == 0) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.GetABatchInferenceOperationResponse getABatchInferenceOperationResponse = new Models.Controller.GetABatchInferenceOperationResponse() + { + operationId = operations[0].tags.operationId, + operationType = operations[0].tags.operationType, + startTimeUtc = operations[0].startTimeUtc, + completeTimeUtc = operations[0].endTimeUtc, + description = operations[0].description, + status = operations[0].status, + error = operations[0].error, + }; + return getABatchInferenceOperationResponse; + } + + public static async Task ListAllInferenceOperationsByUser(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_infer/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var listAllInferenceOperationsByUserRequest = new Models.Controller.Backend.ListAllInferenceOperationsByUserRequest(); + listAllInferenceOperationsByUserRequest.filter = $"runType eq azureml.PipelineRun and tags/operationType eq inference and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(listAllInferenceOperationsByUserRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List operations = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (operations == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.ListAllInferenceOperationsByUserResponse listAllInferenceOperationsByUserResponse = new Models.Controller.ListAllInferenceOperationsByUserResponse(); + foreach (var operation in operations) + { + listAllInferenceOperationsByUserResponse.operations.Add(new Models.Controller.ListAllInferenceOperationsByUserResponse.Operation() + { + operationId = operation.tags.operationId, + operationType = operation.tags.operationType, + startTimeUtc = operation.startTimeUtc, + completeTimeUtc = operation.endTimeUtc, + description = operation.description, + status = operation.status, + error = operation.error, + }); + } + return listAllInferenceOperationsByUserResponse; + } + + public static async Task DeployRealTimePredictionEndpoint(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid modelId, string userInput) + { + var requestUri = new Uri(version.DeployModelAPI); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var endpointId = GetLunaGeneratedUuid(); + var deployRealTimePredictionEndpointRequest = new Models.Controller.Backend.DeployRealTimePredictionEndpointRequest(); + deployRealTimePredictionEndpointRequest.experimentName = $"p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_deploy"; + deployRealTimePredictionEndpointRequest.parameterAssignments.userInput = userInput; + deployRealTimePredictionEndpointRequest.parameterAssignments.endpointId = endpointId; + deployRealTimePredictionEndpointRequest.parameterAssignments.modelId = modelId.ToString("N"); + deployRealTimePredictionEndpointRequest.parameterAssignments.userId = apiSubscription.UserId; + deployRealTimePredictionEndpointRequest.parameterAssignments.productName = product.ProductName; + deployRealTimePredictionEndpointRequest.parameterAssignments.deploymentName = deployment.DeploymentName; + deployRealTimePredictionEndpointRequest.parameterAssignments.apiVersion = version.VersionName; + deployRealTimePredictionEndpointRequest.parameterAssignments.subscriptionId = apiSubscription.SubscriptionId.ToString(); + deployRealTimePredictionEndpointRequest.tags.userId = apiSubscription.UserId; + deployRealTimePredictionEndpointRequest.tags.productName = product.ProductName; + deployRealTimePredictionEndpointRequest.tags.deploymentName = deployment.DeploymentName; + deployRealTimePredictionEndpointRequest.tags.apiVersion = version.VersionName; + deployRealTimePredictionEndpointRequest.tags.modelId = modelId.ToString("N"); + deployRealTimePredictionEndpointRequest.tags.endpointId = endpointId; + deployRealTimePredictionEndpointRequest.tags.operationType = "deployment"; + deployRealTimePredictionEndpointRequest.tags.subscriptionId = apiSubscription.SubscriptionId.ToString(); + + request.Content = new StringContent(JsonConvert.SerializeObject(deployRealTimePredictionEndpointRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + return new Models.Controller.DeployRealTimePredictionEndpointResponse { endpointId = endpointId }; + } + + public static async Task GetADeployOperationByEndpointIdUser(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid endpointId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_deploy/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var getAllDeployOperationsByEndpointIdUserRequest = new Models.Controller.Backend.GetADeployOperationByEndpointIdUserRequest(); + getAllDeployOperationsByEndpointIdUserRequest.filter = $"runType eq azureml.PipelineRun and tags/operationType eq deployment and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId} and tags/endpointId eq {endpointId.ToString("N")}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(getAllDeployOperationsByEndpointIdUserRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List endpoints = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (endpoints == null || endpoints.Count == 0) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + var endpoint = endpoints[0]; + Models.Controller.GetADeployOperationByEndpointIdUserResponse getAllDeployOperationsByEndpointIdAndVerifyUserResponse = new Models.Controller.GetADeployOperationByEndpointIdUserResponse() + { + operationType = endpoint.tags.operationType, + endpointId = endpoint.tags.endpointId, + startTimeUtc = endpoint.startTimeUtc, + completeTimeUtc = endpoint.endTimeUtc, + status = endpoint.status, + error = endpoint.error, + }; + + return getAllDeployOperationsByEndpointIdAndVerifyUserResponse; + } + + public static async Task ListAllDeployOperationsByUser(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/history/v1.0" + workspace.ResourceId + $"/experiments/p_{product.Id}_d_{deployment.Id}_s_{apiSubscription.Id}_deploy/runs:query"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var listAllDeployOperationsByUserRequest = new Models.Controller.Backend.ListAllDeployOperationsByUserRequest(); + listAllDeployOperationsByUserRequest.filter = $"runType eq azureml.PipelineRun and tags/operationType eq deployment and tags/userId eq {apiSubscription.UserId} and tags/subscriptionId eq {apiSubscription.SubscriptionId}"; + + request.Content = new StringContent(JsonConvert.SerializeObject(listAllDeployOperationsByUserRequest)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List endpoints = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (endpoints == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.ListAllDeployOperationsByUserResponse listAllDeployOperationsByUserResponse = new Models.Controller.ListAllDeployOperationsByUserResponse(); + foreach (var endpoint in endpoints) + { + listAllDeployOperationsByUserResponse.operations.Add(new Models.Controller.ListAllDeployOperationsByUserResponse.Operation() + { + operationType = endpoint.tags.operationType, + endpointId = endpoint.tags.endpointId, + startTimeUtc = endpoint.startTimeUtc, + completeTimeUtc = endpoint.endTimeUtc, + status = endpoint.status, + error = endpoint.error, + }); + } + return listAllDeployOperationsByUserResponse; + } + + private static async Task GetServiceKeys(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid endpointId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/services/{endpointId.ToString("N")}/listkeys"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Post }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + Models.Controller.GetServiceKeysResponse getServiceKeysResponse = (Models.Controller.GetServiceKeysResponse)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(Models.Controller.GetServiceKeysResponse)); + if (getServiceKeysResponse == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + return getServiceKeysResponse; + } + + public static async Task GetAllRealTimeServiceEndpointsByUserProductDeployment(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/services?tags=userId={apiSubscription.UserId},productName={product.ProductName},deploymentName={deployment.DeploymentName},subscriptionId={apiSubscription.SubscriptionId}"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List endpoints = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (endpoints == null) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + Models.Controller.GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse getAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse = new Models.Controller.GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse(); + foreach (var endpoint in endpoints) + { + var getServiceKeysResponse = await GetServiceKeys(product, deployment, version, workspace, apiSubscription, new Guid(endpoint.name)); + getAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.endpoints.Add(new Models.Controller.GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.Endpoint() + { + endpointId = endpoint.name, + startTimeUtc = endpoint.createdTime, + completeTimeUtc = endpoint.updatedTime, + scoringUrl = endpoint.scoringUri, + primaryKey = getServiceKeysResponse.primaryKey, + secondaryKey = getServiceKeysResponse.secondaryKey, + description = endpoint.description, + }); + } + return getAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse; + } + + public static async Task GetARealTimeServiceEndpointByEndpointIdUserProductDeployment(Product product, Deployment deployment, APIVersion version, AMLWorkspace workspace, APISubscription apiSubscription, Guid endpointId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/services?tags=userId={apiSubscription.UserId},productName={product.ProductName},deploymentName={deployment.DeploymentName},subscriptionId={apiSubscription.SubscriptionId}&name={endpointId.ToString("N")}"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Get }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + IDictionary result = (IDictionary)System.Text.Json.JsonSerializer.Deserialize(responseContent, typeof(IDictionary)); + if (!result.ContainsKey("value")) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + + List endpoints = (List)System.Text.Json.JsonSerializer.Deserialize(result["value"].ToString(), typeof(List)); + if (endpoints == null || endpoints.Count == 0) + { + throw new LunaServerException($"Query result in bad format. The response is {responseContent}."); + } + + var endpoint = endpoints[0]; + var getServiceKeysResponse = await GetServiceKeys(product, deployment, version, workspace, apiSubscription, new Guid(endpoint.name)); + Models.Controller.GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse getARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse = new Models.Controller.GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse() + { + endpointId = endpoint.name, + startTimeUtc = endpoint.createdTime, + completeTimeUtc = endpoint.updatedTime, + scoringUrl = endpoint.scoringUri, + primaryKey = getServiceKeysResponse.primaryKey, + secondaryKey = getServiceKeysResponse.secondaryKey, + description = endpoint.description, + }; + return getARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse; + } + + public static async Task DeleteAEndpoint(AMLWorkspace workspace, Guid endpointId) + { + var region = await GetRegion(workspace); + + var requestUrl = $"https://{region}.api.azureml.ms/modelmanagement/v1.0" + workspace.ResourceId + $"/services/{endpointId.ToString("N")}"; + var requestUri = new Uri(requestUrl); + var request = new HttpRequestMessage { RequestUri = requestUri, Method = HttpMethod.Delete }; + + var token = await ControllerAuthHelper.GetToken(workspace.AADTenantId.ToString(), workspace.AADApplicationId.ToString(), workspace.AADApplicationSecrets); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await HttpClient.SendAsync(request); + + string responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new LunaServerException($"Query failed with response {responseContent}"); + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/CustomMeteringClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/CustomMeteringClient.cs index 379c27b..be22171 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/CustomMeteringClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/CustomMeteringClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Net; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/ICustomMeteringClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/ICustomMeteringClient.cs index 2ec6563..ffecbfd 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/ICustomMeteringClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/CustomMetering/ICustomMeteringClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/ExceptionUtils.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/ExceptionUtils.cs index fa9d046..9155961 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/ExceptionUtils.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/ExceptionUtils.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Net; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaBadRequestUserException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaBadRequestUserException.cs index 140c93c..f5e3236 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaBadRequestUserException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaBadRequestUserException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Exceptions { public class LunaBadRequestUserException : LunaUserException diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaConflictUserException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaConflictUserException.cs index 5b15d69..3d4e542 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaConflictUserException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaConflictUserException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Exceptions { public class LunaConflictUserException : LunaUserException diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaFulfillmentException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaFulfillmentException.cs index 5552b0f..959451f 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaFulfillmentException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaFulfillmentException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; namespace Luna.Clients.Exceptions diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaNotFoundUserException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaNotFoundUserException.cs index 7d9910f..690a4d0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaNotFoundUserException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaNotFoundUserException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Exceptions { public class LunaNotFoundUserException:LunaUserException diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaProvisioningException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaProvisioningException.cs index 6c88d3d..e5b77cc 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaProvisioningException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaProvisioningException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using Luna.Data.Enums; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaServerException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaServerException.cs index c37dc30..5c99eff 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaServerException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaServerException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; namespace Luna.Clients.Exceptions diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUnauthorizedUserException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUnauthorizedUserException.cs index c21c64f..c1ecca2 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUnauthorizedUserException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUnauthorizedUserException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Exceptions { public class LunaUnauthorizedUserException : LunaUserException diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUserException.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUserException.cs index c5eacdf..b3a3627 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUserException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/LunaUserException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Net; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/UserErrorCode.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/UserErrorCode.cs index 671a0db..e12ee24 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/UserErrorCode.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Exceptions/UserErrorCode.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Exceptions { public enum UserErrorCode @@ -10,6 +13,7 @@ public enum UserErrorCode ParameterNotProvided, ArmTemplateNotProvided, InvalidParameter, - Unauthorized + Unauthorized, + AuthKeyNotProvided } } diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/FluentUriBuilder.cs b/end-to-end-solutions/Luna/src/Luna.Clients/FluentUriBuilder.cs index d1e78b6..538967c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/FluentUriBuilder.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/FluentUriBuilder.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Linq; using System.Web; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/FulfillmentClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/FulfillmentClient.cs index dd31947..2f12007 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/FulfillmentClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/FulfillmentClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Net.Http; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/IFulfillmentClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/IFulfillmentClient.cs index 9deff29..4c0508e 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/IFulfillmentClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/IFulfillmentClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/SubscriptionAction.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/SubscriptionAction.cs index 9f3ea27..262f479 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/SubscriptionAction.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Fulfillment/SubscriptionAction.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.ComponentModel; namespace Luna.Clients.Fulfillment diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/IpAddressing/IpRangeUtility.cs b/end-to-end-solutions/Luna/src/Luna.Clients/IpAddressing/IpRangeUtility.cs index fef7bbb..450f113 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/IpAddressing/IpRangeUtility.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/IpAddressing/IpRangeUtility.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Logging/LoggingUtils.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Logging/LoggingUtils.cs index 3059a4c..14f993b 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Logging/LoggingUtils.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Logging/LoggingUtils.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Net; using System.Net.Http; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Luna.Clients.csproj b/end-to-end-solutions/Luna/src/Luna.Clients/Luna.Clients.csproj index 2a9ad78..1b1513f 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Luna.Clients.csproj +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Luna.Clients.csproj @@ -43,6 +43,7 @@ HACK: Covering the discrepancy between prod API and mock API for gell all subscr + diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/LunaClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/LunaClient.cs index 29e31da..4cd0419 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/LunaClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/LunaClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Net.Http; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/APIVersion.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/APIVersion.cs new file mode 100644 index 0000000..267cfa1 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/APIVersion.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Azure +{ + public class APIVersion + { + public string name { get; set; } + public Properties properties { get; set; } + public class Properties + { + public string displayName { get; set; } + public string apiVersion { get; set; } + public string apiVersionSetId { get; set; } + public string serviceUrl { get; set; } + public string path { get; set; } + public List protocols { get; set; } + } + public APIVersion() + { + this.properties = new Properties(); + this.properties.serviceUrl = "https://luna-dev-controller.azurewebsites.net"; + this.properties.path = ""; + this.properties.protocols = new List(new string[] + { + "https" + }); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/APIVersionSet.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/APIVersionSet.cs new file mode 100644 index 0000000..6a66c91 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/APIVersionSet.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Luna.Clients.Models.Azure +{ + public class APIVersionSet + { + public string name { get; set; } + public Properties properties { get; set; } + public class Properties + { + public string displayName { get; set; } + public string versioningScheme { get; set; } + public string versionQueryName { get; set; } + } + + public APIVersionSet() + { + this.properties = new Properties(); + this.properties.versioningScheme = "Query"; + this.properties.versionQueryName = "api-version"; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Operation.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Operation.cs new file mode 100644 index 0000000..2ca409e --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Operation.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Azure +{ + public class Operation + { + public string name { get; set; } + public Properties properties { get; set; } + public class Properties + { + public string displayName { get; set; } + public string method { get; set; } + public string urlTemplate { get; set; } + public List templateParameters { get; set; } + } + public class templateParameter + { + public string name { get; set; } + } + public Operation() + { + this.properties = new Properties() + { + templateParameters = new List() + }; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/OperationTypeEnum.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/OperationTypeEnum.cs new file mode 100644 index 0000000..8afc301 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/OperationTypeEnum.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Azure +{ + public enum OperationTypeEnum + { + // RTP + RealTimePrediction, + // BI + BatchInferenceWithDefaultModel, + GetABatchInferenceOperationWithDefaultModel, + ListAllInferenceOperationsByUserWithDefaultModel, + // TYOM + TrainModel, + ListAllTrainingOperationsByUser, + GetATrainingOperationsByModelIdUser, + GetAModelByModelIdUserProductDeployment, + GetAllModelsByUserProductDeployment, + DeleteAModel, + BatchInference, + GetABatchInferenceOperation, + ListAllInferenceOperationsByUser, + DeployRealTimePredictionEndpoint, + GetADeployOperationByEndpointIdUser, + ListAllDeployOperationsByUser, + GetAllRealTimeServiceEndpointsByUserProductDeployment, + GetARealTimeServiceEndpointByEndpointIdUserProductDeployment, + DeleteAEndpoint + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Policy.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Policy.cs new file mode 100644 index 0000000..9871904 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Policy.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Azure +{ + public class Policy + { + public Properties properties { get; set; } + public class Properties + { + public string format { get; set; } + public string value { get; set; } + } + public Policy() + { + this.properties = new Properties(); + this.properties.format = "xml"; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Product.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Product.cs new file mode 100644 index 0000000..0d1b1b4 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Product.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Azure +{ + public class Product + { + public string name { get; set; } + public Properties properties { get; set; } + public class Properties + { + public string displayName { get; set; } + public string state { get; set; } + } + public Product() + { + this.properties = new Properties(); + this.properties.state = "Published"; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Subscription.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Subscription.cs new file mode 100644 index 0000000..7e37cee --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/Subscription.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Azure +{ + public class APISubscription + { + public string name { get; set; } + public Properties properties { get; set; } + public class Properties + { + public string ownerId { get; set; } + public string scope { get; set; } + public string displayName { get; set; } + public string state { get; set; } + + public string primaryKey { get; set; } + public string secondaryKey { get; set; } + } + public APISubscription() + { + this.properties = new Properties(); + } + } +} + diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/SubscriptionStatus.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/SubscriptionStatus.cs new file mode 100644 index 0000000..2aff8f8 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/SubscriptionStatus.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Azure +{ + public static class SubscriptionStatus + { + private static IDictionary stateMap = new Dictionary + { + {"Subscribed", "active"}, + {"Suspended", "suspended"} + }; + + public static string GetState(string status) + { + if(!stateMap.ContainsKey(status)) + throw new ArgumentException("The controller type haven't support yet."); + return stateMap[status]; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/User.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/User.cs new file mode 100644 index 0000000..cfc0ddc --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Azure/APIM/Luna.AI/User.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Luna.Clients.Models.Azure +{ + public class User + { + public string name { get; set; } + public Properties properties { get; set; } + public class Properties + { + public string firstName { get; set; } + public string lastName { get; set; } + public string email { get; set; } + public string confirmation { get; set; } + } + public User() + { + this.properties = new Properties(); + this.properties.confirmation = "signup"; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/BatchInferenceRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/BatchInferenceRequest.cs new file mode 100644 index 0000000..cab8885 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/BatchInferenceRequest.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class BatchInferenceRequest + { + public string experimentName { get; set; } + public ParameterAssignments parameterAssignments { get; set; } + public Tags tags { get; set; } + + public class ParameterAssignments + { + public string userInput { get; set; } + public string operationId { get; set; } + public string modelId { get; set; } + } + public class Tags + { + public string userId { get; set; } + public string productName { get; set; } + public string deploymentName { get; set; } + public string apiVersion { get; set; } + public string modelId { get; set; } + public string operationId { get; set; } + public string operationType { get; set; } + public string subscriptionId { get; set; } + } + public BatchInferenceRequest() + { + this.parameterAssignments = new ParameterAssignments() + { + + }; + this.tags = new Tags() + { + operationType = "training", + }; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/BatchInferenceWithDefaultModelRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/BatchInferenceWithDefaultModelRequest.cs new file mode 100644 index 0000000..36673c8 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/BatchInferenceWithDefaultModelRequest.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class BatchInferenceWithDefaultModelRequest + { + public string experimentName { get; set; } + public ParameterAssignments parameterAssignments { get; set; } + public Tags tags { get; set; } + + public class ParameterAssignments + { + public string userInput { get; set; } + public string operationId { get; set; } + } + public class Tags + { + public string userId { get; set; } + public string productName { get; set; } + public string deploymentName { get; set; } + public string apiVersion { get; set; } + public string operationId { get; set; } + public string operationType { get; set; } + public string subscriptionId { get; set; } + } + public BatchInferenceWithDefaultModelRequest() + { + this.parameterAssignments = new ParameterAssignments() + { + + }; + this.tags = new Tags() + { + operationType = "training", + }; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/DeployRealTimePredictionEndpointRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/DeployRealTimePredictionEndpointRequest.cs new file mode 100644 index 0000000..7419889 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/DeployRealTimePredictionEndpointRequest.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class DeployRealTimePredictionEndpointRequest + { + public string experimentName { get; set; } + public ParameterAssignments parameterAssignments { get; set; } + public Tags tags { get; set; } + + public class ParameterAssignments + { + public string userInput { get; set; } + public string endpointId { get; set; } + public string modelId { get; set; } + public string userId { get; set; } + public string productName { get; set; } + public string deploymentName { get; set; } + public string apiVersion { get; set; } + public string subscriptionId { get; set; } + } + public class Tags + { + public string userId { get; set; } + public string productName { get; set; } + public string deploymentName { get; set; } + public string apiVersion { get; set; } + public string modelId { get; set; } + public string endpointId { get; set; } + public string subscriptionId { get; set; } + public string operationType { get; set; } + } + public DeployRealTimePredictionEndpointRequest() + { + this.parameterAssignments = new ParameterAssignments() + { + + }; + this.tags = new Tags() + { + }; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/DeploymentStatus.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/DeploymentStatus.cs new file mode 100644 index 0000000..2ccebf6 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/DeploymentStatus.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class DeploymentStatus + { + public string runUuid { get; set; } + public string rootRunUuid { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Endpoint.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Endpoint.cs new file mode 100644 index 0000000..a2d8820 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Endpoint.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class Endpoint + { + public string id { get; set; } + public string scoringUri { get; set; } + public string sslKey { get; set; } + public String description { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetABatchInferenceOperationRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetABatchInferenceOperationRequest.cs new file mode 100644 index 0000000..5dc0486 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetABatchInferenceOperationRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetABatchInferenceOperationRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetABatchInferenceOperationResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetABatchInferenceOperationResponse.cs new file mode 100644 index 0000000..f3ea4d1 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetABatchInferenceOperationResponse.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetABatchInferenceOperationResponse + { + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public String description { get; set; } + public Object error { get; set; } + public Tags tags { get; set; } + public class Tags + { + public string operationId { get; set; } + public string operationType { get; set; } + } + public GetABatchInferenceOperationResponse() + { + this.tags = new Tags(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetADeployOperationByEndpointIdUserRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetADeployOperationByEndpointIdUserRequest.cs new file mode 100644 index 0000000..d5bcc20 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetADeployOperationByEndpointIdUserRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetADeployOperationByEndpointIdUserRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetADeployOperationByEndpointIdUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetADeployOperationByEndpointIdUserResponse.cs new file mode 100644 index 0000000..0364949 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetADeployOperationByEndpointIdUserResponse.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetADeployOperationByEndpointIdUserResponse + { + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public Object error { get; set; } + public Tags tags { get; set; } + public class Tags + { + public string operationType { get; set; } + public string endpointId { get; set; } + } + public GetADeployOperationByEndpointIdUserResponse() + { + + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAModelByModelIdUserProductDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAModelByModelIdUserProductDeploymentResponse.cs new file mode 100644 index 0000000..0b272f3 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAModelByModelIdUserProductDeploymentResponse.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetAModelByModelIdUserProductDeploymentResponse + { + public string name { get; set; } + public string createdTime { get; set; } + public string modifiedTime { get; set; } + public String description { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse.cs new file mode 100644 index 0000000..1dc0656 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse + { + public string name { get; set; } + public string createdTime { get; set; } + public string updatedTime { get; set; } + public string scoringUri { get; set; } + public String description { get; set; } + public GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse() + { + + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllModelsByUserProductDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllModelsByUserProductDeploymentResponse.cs new file mode 100644 index 0000000..57f8849 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllModelsByUserProductDeploymentResponse.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetAllModelsByUserProductDeploymentResponse + { + public string name { get; set; } + public string createdTime { get; set; } + public string modifiedTime { get; set; } + public String description { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.cs new file mode 100644 index 0000000..7c50586 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse + { + public string name { get; set; } + public string createdTime { get; set; } + public string updatedTime { get; set; } + public string scoringUri { get; set; } + public String description { get; set; } + public GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse() + { + + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdAndVerifyUserRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdAndVerifyUserRequest.cs new file mode 100644 index 0000000..eea5dad --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdAndVerifyUserRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetAllTrainingOperationsByModelIdAndVerifyUserRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdAndVerifyUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdAndVerifyUserResponse.cs new file mode 100644 index 0000000..9883482 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdAndVerifyUserResponse.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetAllTrainingOperationsByModelIdAndVerifyUserResponse + { + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public String description { get; set; } + public Object error { get; set; } + public Tags tags { get; set; } + public class Tags + { + public string modelId { get; set; } + } + public GetAllTrainingOperationsByModelIdAndVerifyUserResponse() + { + + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdUserRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdUserRequest.cs new file mode 100644 index 0000000..36b6aa1 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdUserRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetATrainingOperationsByModelIdUserRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdUserResponse.cs new file mode 100644 index 0000000..5137d2a --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/GetAllTrainingOperationsByModelIdUserResponse.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class GetATrainingOperationsByModelIdUserResponse + { + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public String description { get; set; } + public Object error { get; set; } + public Tags tags { get; set; } + public class Tags + { + public string modelId { get; set; } + public string operationType { get; set; } + } + public GetATrainingOperationsByModelIdUserResponse() + { + + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllDeployOperationsByUserRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllDeployOperationsByUserRequest.cs new file mode 100644 index 0000000..ae8d5fa --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllDeployOperationsByUserRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllDeployOperationsByUserRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllDeployOperationsByUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllDeployOperationsByUserResponse.cs new file mode 100644 index 0000000..4f9b8f9 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllDeployOperationsByUserResponse.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllDeployOperationsByUserResponse + { + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public Object error { get; set; } + public Tags tags { get; set; } + public class Tags + { + public string operationType { get; set; } + public string endpointId { get; set; } + } + public ListAllDeployOperationsByUserResponse() + { + + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllInferenceOperationsByUserRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllInferenceOperationsByUserRequest.cs new file mode 100644 index 0000000..6be7066 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllInferenceOperationsByUserRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllInferenceOperationsByUserRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllInferenceOperationsByUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllInferenceOperationsByUserResponse.cs new file mode 100644 index 0000000..a425039 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllInferenceOperationsByUserResponse.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllInferenceOperationsByUserResponse + { + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public String description { get; set; } + public Object error { get; set; } + public Tags tags { get; set; } + public class Tags + { + public string operationId { get; set; } + public string operationType { get; set; } + } + public ListAllInferenceOperationsByUserResponse() + { + this.tags = new Tags(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByAUserRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByAUserRequest.cs new file mode 100644 index 0000000..185b7d2 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByAUserRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllTrainingOperationsByAUserRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByAUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByAUserResponse.cs new file mode 100644 index 0000000..e222831 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByAUserResponse.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllTrainingOperationsByAUserResponse + { + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public String description { get; set; } + public Object error { get; set; } + public Tags tags { get; set; } + public class Tags + { + public string modelId { get; set; } + public string operationType { get; set; } + } + public ListAllTrainingOperationsByAUserResponse() + { + + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByUserRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByUserRequest.cs new file mode 100644 index 0000000..f217b76 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByUserRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllTrainingOperationsByUserRequest + { + public string filter { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByUserResponse.cs new file mode 100644 index 0000000..6e7aa3e --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ListAllTrainingOperationsByUserResponse.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ListAllTrainingOperationsByUserResponse + { + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Model.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Model.cs new file mode 100644 index 0000000..e9b5e71 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Model.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class Model + { + public string name { get; set; } + public string description { get; set; } + public string createdTime { get; set; } + public string modifiedTime { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ModelTrainingStatus.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ModelTrainingStatus.cs new file mode 100644 index 0000000..9aea1f7 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/ModelTrainingStatus.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class ModelTrainingStatus + { + public string runUuid { get; set; } + public string rootRunUuid { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Operation.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Operation.cs new file mode 100644 index 0000000..7139693 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/Operation.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class Operation + { + public string operationType { get; set; } + public string modelId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public string description { get; set; } + public object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/OperationStatus.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/OperationStatus.cs new file mode 100644 index 0000000..20fec28 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/OperationStatus.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class OperationStatus + { + public string runUuid { get; set; } + public string rootRunUuid { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string endTimeUtc { get; set; } + public String description { get; set; } + public object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/TrainModelRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/TrainModelRequest.cs new file mode 100644 index 0000000..5d70bf7 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/Backend/TrainModelRequest.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller.Backend +{ + public class TrainModelRequest + { + public string experimentName { get; set; } + public ParameterAssignments parameterAssignments { get; set; } + public Tags tags { get; set; } + public class ParameterAssignments + { + public string userInput { get; set; } + public string modelId { get; set; } + public string userId { get; set; } + public string productName { get; set; } + public string deploymentName { get; set; } + public string apiVersion { get; set; } + public string subscriptionId { get; set; } + } + public class Tags + { + public string userId { get; set; } + public string productName { get; set; } + public string deploymentName { get; set; } + public string apiVersion { get; set; } + public string modelId { get; set; } + public string operationId { get; set; } + public string operationType { get; set; } + public string subscriptionId { get; set; } + } + public TrainModelRequest() + { + this.parameterAssignments = new ParameterAssignments() { + + }; + this.tags = new Tags() { + operationType = "training", + }; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/BatchInferenceRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/BatchInferenceRequest.cs new file mode 100644 index 0000000..973dd18 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/BatchInferenceRequest.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Luna.Clients.Models.Controller +{ + public class BatchInferenceRequest + { + public string userId { get; set; } + public Guid subscriptionId { get; set; } + public string userInput { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/BatchInferenceResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/BatchInferenceResponse.cs new file mode 100644 index 0000000..7747c76 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/BatchInferenceResponse.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class BatchInferenceResponse + { + public string operationId { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/DeployRealTimePredictionEndpointRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/DeployRealTimePredictionEndpointRequest.cs new file mode 100644 index 0000000..3ad106e --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/DeployRealTimePredictionEndpointRequest.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class DeployRealTimePredictionEndpointRequest + { + public string userId { get; set; } + public Guid subscriptionId { get; set; } + public IDictionary input { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/DeployRealTimePredictionEndpointResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/DeployRealTimePredictionEndpointResponse.cs new file mode 100644 index 0000000..d96696b --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/DeployRealTimePredictionEndpointResponse.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class DeployRealTimePredictionEndpointResponse + { + public string endpointId { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetABatchInferenceOperationResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetABatchInferenceOperationResponse.cs new file mode 100644 index 0000000..f5d2e43 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetABatchInferenceOperationResponse.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetABatchInferenceOperationResponse + { + public string operationType { get; set; } + public string operationId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public String description { get; set; } + public Object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetADeployOperationByEndpointIdUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetADeployOperationByEndpointIdUserResponse.cs new file mode 100644 index 0000000..cf38906 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetADeployOperationByEndpointIdUserResponse.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetADeployOperationByEndpointIdUserResponse + { + public string operationType { get; set; } + public string endpointId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public Object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetADeployedEndpointResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetADeployedEndpointResponse.cs new file mode 100644 index 0000000..10fdeef --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetADeployedEndpointResponse.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetADeployedEndpointResponse + { + public string deploymentId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public string description { get; set; } + public string scoringUrl { get; set; } + public string key { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAModelByModelIdUserProductDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAModelByModelIdUserProductDeploymentResponse.cs new file mode 100644 index 0000000..6ef4eeb --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAModelByModelIdUserProductDeploymentResponse.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetAModelByModelIdUserProductDeploymentResponse + { + public string modelId { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public String description { get; set; } + public object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAModelResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAModelResponse.cs new file mode 100644 index 0000000..9a0cb01 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAModelResponse.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetAModelResponse + { + public string modelId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public string description { get; set; } + public object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse.cs new file mode 100644 index 0000000..7ef8ec2 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetARealTimeServiceEndpointByEndpointIdUserProductAndDeploymentResponse + { + public string endpointId { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public string scoringUrl { get; set; } + public string primaryKey { get; set; } + public string secondaryKey { get; set; } + public String description { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllDeployedEndpoints.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllDeployedEndpoints.cs new file mode 100644 index 0000000..6738e06 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllDeployedEndpoints.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetAllDeployedEndpoints + { + public string deploymentId { get; set; } + public string scoringUrl { get; set; } + public string key { get; set; } + public String description { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllModelsByUserProductDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllModelsByUserProductDeploymentResponse.cs new file mode 100644 index 0000000..df82437 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllModelsByUserProductDeploymentResponse.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetAllModelsByUserProductDeploymentResponse + { + public List models { get; set; } + public class Model + { + public string modelId { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public String description { get; set; } + } + public GetAllModelsByUserProductDeploymentResponse() + { + this.models = new List(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.cs new file mode 100644 index 0000000..87eefdc --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse + { + public List endpoints { get; set; } + public class Endpoint + { + public string endpointId { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public string scoringUrl { get; set; } + public string primaryKey { get; set; } + public string secondaryKey { get; set; } + public String description { get; set; } + } + public GetAllRealTimeServiceEndpointsByUserProductAndDeploymentResponse() + { + this.endpoints = new List(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllTrainingOperationsByModelIdAndVerifyUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllTrainingOperationsByModelIdAndVerifyUserResponse.cs new file mode 100644 index 0000000..687e350 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllTrainingOperationsByModelIdAndVerifyUserResponse.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetAllTrainingOperationsByModelIdAndVerifyUserResponse + { + public List operations { get; set; } + public class Operation + { + public string modelId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public String description { get; set; } + public object error { get; set; } + } + public GetAllTrainingOperationsByModelIdAndVerifyUserResponse() + { + this.operations = new List(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllTrainingOperationsByModelIdUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllTrainingOperationsByModelIdUserResponse.cs new file mode 100644 index 0000000..4b69b36 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetAllTrainingOperationsByModelIdUserResponse.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetATrainingOperationsByModelIdUserResponse + { + public string operationType { get; set; } + public string modelId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public String description { get; set; } + public object error { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetServiceKeysResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetServiceKeysResponse.cs new file mode 100644 index 0000000..31119dd --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/GetServiceKeysResponse.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class GetServiceKeysResponse + { + public string primaryKey { get; set; } + public string secondaryKey { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllDeployOperationsByUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllDeployOperationsByUserResponse.cs new file mode 100644 index 0000000..647c65b --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllDeployOperationsByUserResponse.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class ListAllDeployOperationsByUserResponse + { + public List operations { get; set; } + public class Operation + { + public string operationType { get; set; } + public string endpointId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public Object error { get; set; } + } + public ListAllDeployOperationsByUserResponse() + { + this.operations = new List(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllInferenceOperationsByUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllInferenceOperationsByUserResponse.cs new file mode 100644 index 0000000..de4456a --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllInferenceOperationsByUserResponse.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class ListAllInferenceOperationsByUserResponse + { + public List operations { get; set; } + public class Operation + { + public string operationType { get; set; } + public string operationId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public String description { get; set; } + public object error { get; set; } + } + public ListAllInferenceOperationsByUserResponse() + { + this.operations = new List(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllTrainingOperationsByUserResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllTrainingOperationsByUserResponse.cs new file mode 100644 index 0000000..e5c35e2 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/ListAllTrainingOperationsByUserResponse.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class ListAllTrainingOperationsByUserResponse + { + public List operations { get; set; } + public class Operation + { + public string operationType { get; set; } + public string modelId { get; set; } + public string status { get; set; } + public string startTimeUtc { get; set; } + public string completeTimeUtc { get; set; } + public String description { get; set; } + public object error { get; set; } + } + public ListAllTrainingOperationsByUserResponse() + { + this.operations = new List(); + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/PredictRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/PredictRequest.cs new file mode 100644 index 0000000..798eb6c --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/PredictRequest.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class PredictRequest + { + public string userId { get; set; } + public Guid subscriptionId { get; set; } + public object userInput { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/TrainModelRequest.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/TrainModelRequest.cs new file mode 100644 index 0000000..bcb72db --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/TrainModelRequest.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class TrainModelRequest + { + public string userId { get; set; } + public Guid subscriptionId { get; set; } + public string userInput { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/TrainModelResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/TrainModelResponse.cs new file mode 100644 index 0000000..4e9eea7 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Controller/TrainModelResponse.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Clients.Models.Controller +{ + public class TrainModelResponse + { + public string modelId { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/BatchUsageEvent.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/BatchUsageEvent.cs index cf5c0b9..29c424b 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/BatchUsageEvent.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/BatchUsageEvent.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeterEventStatus.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeterEventStatus.cs index 75d166b..ea4b59f 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeterEventStatus.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeterEventStatus.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringAzureTableEntity.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringAzureTableEntity.cs index 2132a8c..775ea2b 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringAzureTableEntity.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringAzureTableEntity.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBadRequestResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBadRequestResult.cs index e4ad27d..2feed84 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBadRequestResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBadRequestResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; namespace Luna.Clients.Models.CustomMetering diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBatchSuccessResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBatchSuccessResult.cs index 18eb15a..eb6f364 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBatchSuccessResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringBatchSuccessResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using Microsoft.WindowsAzure.Storage.Table; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringConflictResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringConflictResult.cs index 22c9d68..7ef5a1b 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringConflictResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringConflictResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.CustomMetering { public class AdditionalInfo diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringError.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringError.cs index 8dcf749..39a70a2 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringError.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringError.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringForbiddenResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringForbiddenResult.cs index f30ae4b..a1796b7 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringForbiddenResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringForbiddenResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.CustomMetering { public class CustomMeteringForbiddenResult : CustomMeteringRequestResult diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringRequestResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringRequestResult.cs index c17d363..cfdc2ef 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringRequestResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringRequestResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Clients.Models.Fulfillment; namespace Luna.Clients.Models.CustomMetering diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringSuccessResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringSuccessResult.cs index a9e52f0..514516b 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringSuccessResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/CustomMeteringSuccessResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.CustomMetering { public class CustomMeteringSuccessResult : CustomMeteringRequestResult diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/FailedCustomMeteringAzureTableEntity.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/FailedCustomMeteringAzureTableEntity.cs index ef131ad..8b611c1 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/FailedCustomMeteringAzureTableEntity.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/FailedCustomMeteringAzureTableEntity.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/Usage.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/Usage.cs index c7224ec..76d46ad 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/Usage.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/CustomMetering/Usage.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.CustomMetering { public class Usage diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/ErrorModel.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/ErrorModel.cs index 92105f8..695f6c1 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/ErrorModel.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/ErrorModel.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Luna.Clients.Exceptions; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ActivatedSubscriptionResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ActivatedSubscriptionResult.cs index 721dfa5..ba8fa5d 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ActivatedSubscriptionResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ActivatedSubscriptionResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Fulfillment { /// diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/FulfillmentRequestResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/FulfillmentRequestResult.cs index da4b45a..1a3d4b0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/FulfillmentRequestResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/FulfillmentRequestResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Fulfillment { public class FulfillmentRequestResult : HttpRequestResult diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationStatusEnum.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationStatusEnum.cs index 9ae88a8..809688b 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationStatusEnum.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationStatusEnum.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Fulfillment { /// diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdate.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdate.cs index c923ffa..ea2258d 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdate.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdate.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Fulfillment { /// diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdateStatusEnum.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdateStatusEnum.cs index 70ded33..6d0f735 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdateStatusEnum.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/OperationUpdateStatusEnum.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Fulfillment { /// diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ResolvedSubscriptionResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ResolvedSubscriptionResult.cs index e83b0ea..32a6cce 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ResolvedSubscriptionResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/ResolvedSubscriptionResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using Newtonsoft.Json; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionOperation.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionOperation.cs index bf0f1d9..f88543f 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionOperation.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionOperation.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionPlans.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionPlans.cs index 4d9447a..e0f7fb3 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionPlans.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionPlans.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; namespace Luna.Clients.Models.Fulfillment diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionResult.cs index 2d1cd19..d4a9f88 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/SubscriptionResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using Newtonsoft.Json; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/Term.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/Term.cs index 0bf401e..d6bee67 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/Term.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/Term.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/UpdateOrDeleteSubscriptionRequestResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/UpdateOrDeleteSubscriptionRequestResult.cs index 7fc5430..5192d6c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/UpdateOrDeleteSubscriptionRequestResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/UpdateOrDeleteSubscriptionRequestResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Linq; using System.Net.Http.Headers; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/WebhookContent.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/WebhookContent.cs index 884faaf..b957ff8 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/WebhookContent.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Fulfillment/WebhookContent.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using Newtonsoft.Json; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/HttpRequestResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/HttpRequestResult.cs index f76a819..11d9d81 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/HttpRequestResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/HttpRequestResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentEnums.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentEnums.cs index 9667772..db86744 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentEnums.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentEnums.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Provisioning { public enum DeploymentMode diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestBody.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestBody.cs index 2fd80aa..58c7828 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestBody.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestBody.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Provisioning { public class DeploymentRequestBody diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestDefinitions.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestDefinitions.cs index 7e168b5..91fcdb5 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestDefinitions.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestDefinitions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Provisioning { /// diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestResult.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestResult.cs index 1465473..8934fa5 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Models/Provisioning/DeploymentRequestResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Clients.Models.Provisioning { public class DeploymentRequestResult : HttpRequestResult diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/IProvisioningClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/IProvisioningClient.cs index fa66e92..b6eabfa 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/IProvisioningClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/IProvisioningClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/ProvisioningClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/ProvisioningClient.cs index a41f126..9903869 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/ProvisioningClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/Provisioning/ProvisioningClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Net.Http; using System.Threading; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/RestClient.cs b/end-to-end-solutions/Luna/src/Luna.Clients/RestClient.cs index c4c45f3..e2b49e3 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/RestClient.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/RestClient.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Net.Http; using System.Net.Http.Headers; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/SecuredClientConfiguration.cs b/end-to-end-solutions/Luna/src/Luna.Clients/SecuredClientConfiguration.cs index 9761bb0..d21093a 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/SecuredClientConfiguration.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/SecuredClientConfiguration.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Clients.Azure.Auth; namespace Luna.Clients diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/CustomMeterEvent.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/CustomMeterEvent.cs index 8d0a210..1f23a39 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/CustomMeterEvent.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/CustomMeterEvent.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/ITelemetryDataConnector.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/ITelemetryDataConnector.cs index 5f95f48..ca3d948 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/ITelemetryDataConnector.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/ITelemetryDataConnector.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsConfiguration.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsConfiguration.cs index 9a3f5f5..cc6580a 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsConfiguration.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsConfiguration.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsTelemetryDataConnector.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsTelemetryDataConnector.cs index 19cfa1d..95bcbb7 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsTelemetryDataConnector.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/LogAnalytics/LogAnalyticsTelemetryDataConnector.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/QueryResponse.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/QueryResponse.cs index 48c2cd2..6b53fa5 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/QueryResponse.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/QueryResponse.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SQLConfiguration.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SQLConfiguration.cs index d436dd1..c09aff0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SQLConfiguration.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SQLConfiguration.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SqlTelemetryDataConnector.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SqlTelemetryDataConnector.cs index 050f6a7..e74524d 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SqlTelemetryDataConnector.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/SQL/SqlTelemetryDataConnector.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Net.Http; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorManager.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorManager.cs index d7f9f8e..bd2a945 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorManager.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorManager.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Net.Http; diff --git a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorTypes.cs b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorTypes.cs index e7cfc85..83ee910 100644 --- a/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorTypes.cs +++ b/end-to-end-solutions/Luna/src/Luna.Clients/TelemetryDataConnectors/TelemetryDataConnectorTypes.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/AMLPipeline.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/AMLPipeline.cs new file mode 100644 index 0000000..c23df26 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/AMLPipeline.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Data.DataContracts.Luna.AI +{ + public class AMLPipeline + { + public string DisplayName { get; set; } + public string Id { get; set; } + public string CreatedDate { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/APISubscriptionKeyName.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/APISubscriptionKeyName.cs new file mode 100644 index 0000000..4a8f148 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/APISubscriptionKeyName.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Data.DataContracts +{ + public class APISubscriptionKeyName + { + public string KeyName { get; set; } + + public APISubscriptionKeyName() + { + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/APIVersionSourceType.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/APIVersionSourceType.cs new file mode 100644 index 0000000..f237cfe --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/APIVersionSourceType.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Data.DataContracts.Luna.AI +{ + public class APIVersionSourceType + { + public string DisplayName { get; set; } + public string id { get; set; } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/HostType.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/HostType.cs new file mode 100644 index 0000000..fcd3d81 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/HostType.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Data.DataContracts.Luna.AI +{ + public class HostType + { + public string DisplayName { get; set; } + public string Id { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/ProductType.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/ProductType.cs new file mode 100644 index 0000000..1e0f007 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/Luna.AI/ProductType.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Luna.Data.DataContracts.Luna.AI +{ + public class ProductType + { + public string DisplayName { get; set; } + public string Id { get; set; } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/OfferWarning.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/OfferWarning.cs index 13136a3..9b2dae0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/OfferWarning.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/OfferWarning.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionProvision.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionProvision.cs index 7e248e8..ab4314e 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionProvision.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionProvision.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionWarning.cs b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionWarning.cs index 52d8594..0e5d29b 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionWarning.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/DataContracts/SubscriptionWarning.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/AadSecretTmp.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/AadSecretTmp.cs index 8f00d87..792fb5c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/AadSecretTmp.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/AadSecretTmp.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplate.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplate.cs index 6b42859..3373717 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplate.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplate.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Data.Entities; using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateArmTemplateParameter.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateArmTemplateParameter.cs index 489ffed..1d0a3e6 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateArmTemplateParameter.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateArmTemplateParameter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Entities { /// diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateParameter.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateParameter.cs index 940c4ce..e0f6f18 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateParameter.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/ArmTemplateParameter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Data.Entities; using System.Collections; using System.Collections.Generic; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeter.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeter.cs index 8d1945f..62a0b33 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeter.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeterDimension.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeterDimension.cs index 7b5bb8d..2579be8 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeterDimension.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/CustomMeterDimension.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpAddress.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpAddress.cs index 5801038..a577ee2 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpAddress.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpAddress.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpBlock.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpBlock.cs index 7607ce6..61d6227 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpBlock.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpBlock.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpConfig.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpConfig.cs index 5c2b4e6..4999e83 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpConfig.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/IpConfig.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/AMLWorkspace.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/AMLWorkspace.cs new file mode 100644 index 0000000..54c1972 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/AMLWorkspace.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Luna.Data.Entities +{ + /// + /// Entity class that maps to the offers table in the database. + /// + public partial class AMLWorkspace + { + /// + /// Constructs the EF Core collection navigation properties. + /// + public AMLWorkspace() + { + } + + /// + /// Copies all non-EF Core values. + /// + /// The object to be copied. + public void Copy(AMLWorkspace workspace) + { + Region = workspace.Region; + ResourceId = workspace.ResourceId; + AADApplicationId = workspace.AADApplicationId; + AADApplicationSecretName = workspace.AADApplicationSecretName; + AADTenantId = workspace.AADTenantId; + } + + [Key] + [JsonIgnore] + public long Id { get; set; } + + public string WorkspaceName { get; set; } + + public string Region { get; set; } + + public string ResourceId { get; set; } + + public Guid AADApplicationId { get; set; } + + [NotMapped] + public string AADApplicationSecrets { get; set; } + + [JsonIgnore] + public string AADApplicationSecretName { get; set; } + + public Guid AADTenantId { get; set; } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/APISubscription.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/APISubscription.cs new file mode 100644 index 0000000..5334a73 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/APISubscription.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Luna.Data.Entities +{ + /// + /// Entity class that maps to the offers table in the database. + /// + public partial class APISubscription + { + /// + /// Constructs the EF Core collection navigation properties. + /// + public APISubscription() + { + } + + /// + /// Copies all non-EF Core values. + /// + /// The object to be copied. + public void Copy(APISubscription subscription) + { + this.ProductName = subscription.ProductName; + this.DeploymentName = subscription.DeploymentName; + this.BaseUrl = subscription.BaseUrl; + this.Status = subscription.Status; + } + + [Key] + [JsonIgnore] + public long Id { get; set; } + + public Guid SubscriptionId { get; set; } + + public string SubscriptionName { get; set; } + + [JsonIgnore] + public long DeploymentId { get; set; } + + [NotMapped] + public string ProductName { get; set; } + + [NotMapped] + public string DeploymentName { get; set; } + + public string UserId { get; set; } + + public string Status { get; set; } + + public string BaseUrl { get; set; } + + public string PrimaryKey { get; set; } + + public string SecondaryKey { get; set; } + + public DateTime CreatedTime { get; set; } + + public DateTime LastUpdatedTime { get; set; } + + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/APIVersion.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/APIVersion.cs new file mode 100644 index 0000000..e9ac7f0 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/APIVersion.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Luna.Data.Entities +{ + /// + /// Entity class that maps to the offers table in the database. + /// + public partial class APIVersion + { + /// + /// Constructs the EF Core collection navigation properties. + /// + public APIVersion() + { + } + + /// + /// Copies all non-EF Core values. + /// + /// The object to be copied. + public void Copy(APIVersion version) + { + this.ProductName = version.ProductName; + this.DeploymentName = version.DeploymentName; + this.RealTimePredictAPI = version.RealTimePredictAPI; + this.BatchInferenceAPI = version.BatchInferenceAPI; + this.TrainModelAPI = version.TrainModelAPI; + this.DeployModelAPI = version.DeployModelAPI; + this.AuthenticationType = version.AuthenticationType; + this.AuthenticationKey = version.AuthenticationKey; + this.VersionSourceType = version.VersionSourceType; + this.GitPersonalAccessToken = version.GitPersonalAccessToken; + this.GitUrl = version.GitUrl; + this.GitVersion = version.GitVersion; + } + + public string GetVersionIdFormat() + { + return VersionName.Replace(".", "-"); + } + + [Key] + [System.Text.Json.Serialization.JsonIgnore] + public long Id { get; set; } + [System.Text.Json.Serialization.JsonIgnore] + public long DeploymentId { get; set; } + [NotMapped] + public string ProductName { get; set; } + [NotMapped] + public string DeploymentName { get; set; } + + public string VersionName { get; set; } + [NotMapped] + public string TrainModelId { get; set; } + [NotMapped] + public string BatchInferenceId { get; set; } + [NotMapped] + public string DeployModelId { get; set; } + + public string RealTimePredictAPI { get; set; } + + public string TrainModelAPI { get; set; } + + public string BatchInferenceAPI { get; set; } + + public string DeployModelAPI { get; set; } + + public string AuthenticationType { get; set; } + + [NotMapped] + public string AuthenticationKey { get; set; } + + [JsonIgnore] + public string AuthenticationKeySecretName { get; set; } + + [System.Text.Json.Serialization.JsonIgnore] + public long AMLWorkspaceId { get; set; } + + [NotMapped] + public string AMLWorkspaceName { get; set; } + + public string AdvancedSettings { get; set; } + + public DateTime CreatedTime { get; set; } + + public DateTime LastUpdatedTime { get; set; } + public string GitUrl { get; set; } + + [NotMapped] + public string GitPersonalAccessToken { get; set; } + + [JsonIgnore] + public string GitPersonalAccessTokenSecretName { get; set; } + + public string GitVersion { get; set; } + + public string VersionSourceType { get; set; } + + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/Deployment.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/Deployment.cs new file mode 100644 index 0000000..1619b0b --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/Deployment.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Luna.Data.Entities +{ + /// + /// Entity class that maps to the offers table in the database. + /// + public partial class Deployment + { + /// + /// Constructs the EF Core collection navigation properties. + /// + public Deployment() + { + } + + /// + /// Copies all non-EF Core values. + /// + /// The object to be copied. + public void Copy(Deployment deployment) + { + this.ProductName = deployment.ProductName; + this.Description = deployment.Description; + } + + [Key] + [JsonIgnore] + public long Id { get; set; } + + [JsonIgnore] + public long ProductId { get; set; } + + [NotMapped] + public string ProductName { get; set; } + + public string DeploymentName { get; set; } + + public string Description { get; set; } + + public DateTime CreatedTime { get; set; } + + public DateTime LastUpdatedTime { get; set; } + + [JsonIgnore] + public virtual Product Product { get; set; } + + [JsonIgnore] + public virtual ICollection Versions { get; set; } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/Product.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/Product.cs new file mode 100644 index 0000000..8fc278a --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Luna.AI/Product.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace Luna.Data.Entities +{ + /// + /// Entity class that maps to the offers table in the database. + /// + public partial class Product + { + /// + /// Constructs the EF Core collection navigation properties. + /// + public Product() + { + } + + /// + /// Copies all non-EF Core values. + /// + /// The object to be copied. + public void Copy(Product product) + { + } + + + [Key] + [JsonIgnore] + public long Id { get; set; } + + public string ProductName { get; set; } + + public string ProductType { get; set; } + + public string HostType { get; set; } + + public string Owner { get; set; } + + public DateTime CreatedTime { get; set; } + + public DateTime LastUpdatedTime { get; set; } + + [JsonIgnore] + public virtual ICollection Deployments { get; set; } + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Offer.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Offer.cs index dbbf6a7..142a180 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Offer.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Offer.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/OfferParameter.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/OfferParameter.cs index 8ddd284..c0665dd 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/OfferParameter.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/OfferParameter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Text.Json.Serialization; namespace Luna.Data.Entities diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Plan.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Plan.cs index 27913a2..4336140 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Plan.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Plan.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/RestrictedUser.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/RestrictedUser.cs index ebe4596..d686915 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/RestrictedUser.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/RestrictedUser.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Subscription.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Subscription.cs index 55ac45c..c189487 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Subscription.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Subscription.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionCustomMeterUsage.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionCustomMeterUsage.cs index f4a1e4f..b6e84dd 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionCustomMeterUsage.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionCustomMeterUsage.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionParameter.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionParameter.cs index a9a0816..b4b4eb9 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionParameter.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/SubscriptionParameter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/TelemetryDataConnector.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/TelemetryDataConnector.cs index a7c1111..63d27e6 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/TelemetryDataConnector.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/TelemetryDataConnector.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Webhook.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Webhook.cs index 0f678c8..6804489 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/Webhook.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/Webhook.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Data.Entities; using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookParameter.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookParameter.cs index 88c76f2..242f224 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookParameter.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookParameter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Data.Entities; using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookWebhookParameter.cs b/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookWebhookParameter.cs index 196a1df..5bc1a5a 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookWebhookParameter.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Entities/WebhookWebhookParameter.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Entities { /// diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/AllowedCustomerOperation.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/AllowedCustomerOperation.cs index c749540..3cb8c21 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/AllowedCustomerOperation.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/AllowedCustomerOperation.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Enums { public enum AllowedCustomerOperation diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/ArmProvisioningState.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/ArmProvisioningState.cs index 14e99b0..a83188f 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/ArmProvisioningState.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/ArmProvisioningState.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Data.Common; diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/FulfillmentState.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/FulfillmentState.cs index 52fde7d..46ba906 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/FulfillmentState.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/FulfillmentState.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Enums { public enum FulfillmentState diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/Luna.AI/Product/HostType.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/Luna.AI/Product/HostType.cs new file mode 100644 index 0000000..cf2ed35 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/Luna.AI/Product/HostType.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Luna.Data.Enums +{ + public enum HostType + { + SaaS, + BYOC // Bring Your Own Compute (for stage 3) + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/Luna.AI/Product/ProductType.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/Luna.AI/Product/ProductType.cs new file mode 100644 index 0000000..e3756ac --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/Luna.AI/Product/ProductType.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Luna.Data.Enums +{ + public enum ProductType + { + RTP, // Real-Time Prediction + BI, // Batch Inference + TYOM // Train Your Own Model + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/OfferStatus.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/OfferStatus.cs index 7f26dc2..a079ecf 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/OfferStatus.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/OfferStatus.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Enums { public enum OfferStatus diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningState.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningState.cs index e1cba84..3c31c93 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningState.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningState.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Enums { public enum ProvisioningState diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningType.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningType.cs index f96ecd6..e43a0cb 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningType.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/ProvisioningType.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Enums { public enum ProvisioningType diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSandboxType.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSandboxType.cs index 8031390..99cfa36 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSandboxType.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSandboxType.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Enums { public enum SubscriptionSandboxType diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSessionMode.cs b/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSessionMode.cs index 1b9490d..cf4186f 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSessionMode.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Enums/SubscriptionSessionMode.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Data.Enums { public enum SubscriptionSessionMode diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Repository/ISqlDbContext.cs b/end-to-end-solutions/Luna/src/Luna.Data/Repository/ISqlDbContext.cs index 5d566b3..739db54 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Repository/ISqlDbContext.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Repository/ISqlDbContext.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Threading.Tasks; using Luna.Data.Entities; using Microsoft.EntityFrameworkCore; @@ -30,6 +33,13 @@ public interface ISqlDbContext DbSet TelemetryDataConnectors { get; set; } + DbSet Products { get; set; } + DbSet Deployments { get; set; } + DbSet APIVersions { get; set; } + DbSet AMLWorkspaces { get; set; } + + DbSet APISubscriptions { get; set; } + // Wrappers for DbContext methods that are used Task _SaveChangesAsync(); Task BeginTransactionAsync(); diff --git a/end-to-end-solutions/Luna/src/Luna.Data/Repository/SqlDbContext.cs b/end-to-end-solutions/Luna/src/Luna.Data/Repository/SqlDbContext.cs index 687f8eb..e1319d7 100644 --- a/end-to-end-solutions/Luna/src/Luna.Data/Repository/SqlDbContext.cs +++ b/end-to-end-solutions/Luna/src/Luna.Data/Repository/SqlDbContext.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Threading.Tasks; using Luna.Data.Entities; using Microsoft.EntityFrameworkCore; @@ -35,6 +38,11 @@ public SqlDbContext(DbContextOptions options) public DbSet WebhookWebhookParameters { get; set; } public DbSet SubscriptionCustomMeterUsages { get; set; } public DbSet TelemetryDataConnectors { get; set; } + public DbSet Products { get; set; } + public DbSet Deployments { get; set; } + public DbSet APIVersions { get; set; } + public DbSet AMLWorkspaces { get; set; } + public DbSet APISubscriptions { get; set; } // Wrappers for DbContext methods that are used public async Task _SaveChangesAsync() diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/MANIFEST.in b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/MANIFEST.in new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/build/lib/luna/__init__.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/build/lib/luna/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/build/lib/luna/lunaClient.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/build/lib/luna/lunaClient.py new file mode 100644 index 0000000..4e5eeff --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/build/lib/luna/lunaClient.py @@ -0,0 +1,138 @@ +import yaml +import io +import argparse +import json +import os +import requests +import time + + +TRAINING_URL_FORMAT = "{base_url}/train?api-version={api_version}" +BATCHINFERENCE_URL_FORMAT = "{base_url}/models/{model_id}/batchinference?api-version={api_version}" +DEPLOY_URL_FORMAT = "{base_url}/models/{model_id}/deploy?api-version={api_version}" +GET_TRAINING_OP_URL_FORMAT = "{base_url}/operations/training/{model_id}?api-version={api_version}" +GET_INFERENCE_OP_URL_FORMAT = "{base_url}/operations/inference/{operation_id}?api-version={api_version}" +GET_DEPLOYMENT_OP_URL_FORMAT = "{base_url}/operations/deployment/{endpoint_id}?api-version={api_version}" + +GET_MODEL_URL_FORMAT = "{base_url}/models/{model_id}?api-version={api_version}" +GET_ENDPOINT_URL_FORMAT = "{base_url}/endpoints/{endpoint_id}?api-version={api_version}" + +class LunaClient(object): + + def __init__(self, base_url, key, api_version): + self._base_url = base_url + self._key = key + self._api_version = api_version + + + def get_request_header(self): + return {"Accept": "application/json", "Ocp-Apim-Subscription-Key": self._key} + + def train_model(self, user_input): + training_url = TRAINING_URL_FORMAT.format(base_url=self._base_url, api_version=self._api_version) + response = requests.post(training_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['modelId'] + + return None + + def get_training_operation(self, model_id): + train_op_url = GET_TRAINING_OP_URL_FORMAT.format(base_url = self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.get(train_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_batch_inference_operation(self, operation_id): + inference_op_url = GET_INFERENCE_OP_URL_FORMAT.format(base_url = self._base_url, operation_id=operation_id, api_version=self._api_version) + response = requests.get(inference_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_deployment_operation(self, endpoint_id): + inference_op_url = GET_DEPLOYMENT_OP_URL_FORMAT.format(base_url = self._base_url, endpoint_id=endpoint_id, api_version=self._api_version) + response = requests.get(inference_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_model(self, model_id): + model_url = GET_MODEL_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.get(model_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + + def get_deployed_endpoint(self, endpoint_id): + model_url = GET_ENDPOINT_URL_FORMAT.format(base_url=self._base_url, endpoint_id=endpoint_id, api_version=self._api_version) + response = requests.get(model_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + def wait_for_training_completion(self, model_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_training_operation(model_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + if op["status"] == 'Completed': + return self.get_model(model_id) + else: + return op + + def wait_for_batch_inference_completion(self, operation_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_batch_inference_operation(operation_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + return op + + def wait_for_deployment_completion(self, endpoint_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_deployment_operation(endpoint_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + return op + + def batch_inference(self, model_id, user_input): + inference_url = BATCHINFERENCE_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.post(inference_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['operationId'] + + return None + + def deploy_model(self, model_id, dns_name_label): + deploy_url = DEPLOY_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + user_input = {"dns_name_label": dns_name_label} + response = requests.post(deploy_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['endpointId'] + + return None + + def score_with_deployed_endpoint(self, endpoint_id, records, verboseMode=False): + endpoint = self.get_deployed_endpoint(endpoint_id) + header = {"Content-Type": "application/json", "Authorization": "Bearer "+endpoint["primaryKey"]} + + userInput = {"records": records, "verboseMode": verboseMode} + + response = requests.post(endpoint["scoringUrl"], headers=header, data=json.dumps(userInput)) + + return response.text + diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/dist/luna-client-0.1.0.tar.gz b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/dist/luna-client-0.1.0.tar.gz new file mode 100644 index 0000000..768f540 Binary files /dev/null and b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/dist/luna-client-0.1.0.tar.gz differ diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/dist/luna_client-0.1.0-py3-none-any.whl b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/dist/luna_client-0.1.0-py3-none-any.whl new file mode 100644 index 0000000..652ab2f Binary files /dev/null and b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/dist/luna_client-0.1.0-py3-none-any.whl differ diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna/__init__.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna/lunaClient.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna/lunaClient.py new file mode 100644 index 0000000..4e5eeff --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna/lunaClient.py @@ -0,0 +1,138 @@ +import yaml +import io +import argparse +import json +import os +import requests +import time + + +TRAINING_URL_FORMAT = "{base_url}/train?api-version={api_version}" +BATCHINFERENCE_URL_FORMAT = "{base_url}/models/{model_id}/batchinference?api-version={api_version}" +DEPLOY_URL_FORMAT = "{base_url}/models/{model_id}/deploy?api-version={api_version}" +GET_TRAINING_OP_URL_FORMAT = "{base_url}/operations/training/{model_id}?api-version={api_version}" +GET_INFERENCE_OP_URL_FORMAT = "{base_url}/operations/inference/{operation_id}?api-version={api_version}" +GET_DEPLOYMENT_OP_URL_FORMAT = "{base_url}/operations/deployment/{endpoint_id}?api-version={api_version}" + +GET_MODEL_URL_FORMAT = "{base_url}/models/{model_id}?api-version={api_version}" +GET_ENDPOINT_URL_FORMAT = "{base_url}/endpoints/{endpoint_id}?api-version={api_version}" + +class LunaClient(object): + + def __init__(self, base_url, key, api_version): + self._base_url = base_url + self._key = key + self._api_version = api_version + + + def get_request_header(self): + return {"Accept": "application/json", "Ocp-Apim-Subscription-Key": self._key} + + def train_model(self, user_input): + training_url = TRAINING_URL_FORMAT.format(base_url=self._base_url, api_version=self._api_version) + response = requests.post(training_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['modelId'] + + return None + + def get_training_operation(self, model_id): + train_op_url = GET_TRAINING_OP_URL_FORMAT.format(base_url = self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.get(train_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_batch_inference_operation(self, operation_id): + inference_op_url = GET_INFERENCE_OP_URL_FORMAT.format(base_url = self._base_url, operation_id=operation_id, api_version=self._api_version) + response = requests.get(inference_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_deployment_operation(self, endpoint_id): + inference_op_url = GET_DEPLOYMENT_OP_URL_FORMAT.format(base_url = self._base_url, endpoint_id=endpoint_id, api_version=self._api_version) + response = requests.get(inference_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_model(self, model_id): + model_url = GET_MODEL_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.get(model_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + + def get_deployed_endpoint(self, endpoint_id): + model_url = GET_ENDPOINT_URL_FORMAT.format(base_url=self._base_url, endpoint_id=endpoint_id, api_version=self._api_version) + response = requests.get(model_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + def wait_for_training_completion(self, model_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_training_operation(model_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + if op["status"] == 'Completed': + return self.get_model(model_id) + else: + return op + + def wait_for_batch_inference_completion(self, operation_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_batch_inference_operation(operation_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + return op + + def wait_for_deployment_completion(self, endpoint_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_deployment_operation(endpoint_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + return op + + def batch_inference(self, model_id, user_input): + inference_url = BATCHINFERENCE_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.post(inference_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['operationId'] + + return None + + def deploy_model(self, model_id, dns_name_label): + deploy_url = DEPLOY_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + user_input = {"dns_name_label": dns_name_label} + response = requests.post(deploy_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['endpointId'] + + return None + + def score_with_deployed_endpoint(self, endpoint_id, records, verboseMode=False): + endpoint = self.get_deployed_endpoint(endpoint_id) + header = {"Content-Type": "application/json", "Authorization": "Bearer "+endpoint["primaryKey"]} + + userInput = {"records": records, "verboseMode": verboseMode} + + response = requests.post(endpoint["scoringUrl"], headers=header, data=json.dumps(userInput)) + + return response.text + diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/PKG-INFO b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/PKG-INFO new file mode 100644 index 0000000..b9151f5 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/PKG-INFO @@ -0,0 +1,18 @@ +Metadata-Version: 2.1 +Name: luna-client +Version: 0.1.0 +Summary: Python client library for project Luna +Home-page: https://github.com/Azure/AIPlatform/end-to-end-solutions/Luna/src/Luna.Packages/luna-client +Author: Xiaochen Wu +Author-email: xiwu@microsoft.com +License: MIT +Description: # Python client library for Project Luna + + ## Overview + + ### +Platform: UNKNOWN +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Description-Content-Type: text/markdown diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/SOURCES.txt b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/SOURCES.txt new file mode 100644 index 0000000..0cdea06 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/SOURCES.txt @@ -0,0 +1,9 @@ +MANIFEST.in +setup.py +luna/__init__.py +luna/lunaClient.py +luna_client.egg-info/PKG-INFO +luna_client.egg-info/SOURCES.txt +luna_client.egg-info/dependency_links.txt +luna_client.egg-info/requires.txt +luna_client.egg-info/top_level.txt \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/dependency_links.txt b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/requires.txt b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/requires.txt new file mode 100644 index 0000000..9edc978 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/requires.txt @@ -0,0 +1,2 @@ +azureml-sdk +mlflow diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/top_level.txt b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/top_level.txt new file mode 100644 index 0000000..9304a26 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/luna_client.egg-info/top_level.txt @@ -0,0 +1 @@ +luna diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/readme.md b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/readme.md new file mode 100644 index 0000000..6ce366c --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/readme.md @@ -0,0 +1,5 @@ +# Python client library for Project Luna + +## Overview + +### \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/setup.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/setup.py new file mode 100644 index 0000000..7131707 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-client/setup.py @@ -0,0 +1,30 @@ +import pathlib +from setuptools import setup + +# The directory containing this file +HERE = pathlib.Path(__file__).parent + +# The text of the README file +README = (HERE / "README.md").read_text() + +# This call to setup() does all the work +setup( + name="luna-client", + version="0.1.0", + description="Python client library for project Luna", + long_description=README, + long_description_content_type="text/markdown", + url="https://github.com/Azure/AIPlatform/end-to-end-solutions/Luna/src/Luna.Packages/luna-client", + author="Xiaochen Wu", + author_email="xiwu@microsoft.com", + license="MIT", + classifiers=[ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + ], + packages=["luna"], + include_package_data=True, + install_requires=["azureml-sdk", "mlflow"], + entry_points={}, +) \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/MANIFEST.in b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/MANIFEST.in new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/__init__.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/azuremlLunaUtils.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/azuremlLunaUtils.py new file mode 100644 index 0000000..5d979a3 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/azuremlLunaUtils.py @@ -0,0 +1,101 @@ +from azureml.core import Workspace, Run, Experiment +from azureml.core.compute import ComputeTarget, AmlCompute +from azureml.core.environment import Environment +from azureml.core.model import Model, InferenceConfig +from azureml.core.runconfig import RunConfiguration +from azureml.core.webservice import AksWebservice, AciWebservice, Webservice +from azureml.pipeline.core.graph import PipelineParameter +from azureml.pipeline.steps import PythonScriptStep +from azureml.pipeline.core import Pipeline + +from luna.baseLunaUtils import BaseLunaUtils +from luna.logging.azuremlLunaLogger import AzureMLLunaLogger + +import os +import yaml + +class AzureMLLunaUtils(BaseLunaUtils): + + def Init(self, luna_config, run_mode, args, userInput): + super().Init(luna_config, run_mode, args, userInput) + self._logger = AzureMLLunaLogger() + + def GetAMLWorkspace(self): + try: + run = Run.get_context(allow_offline=False) + return run.experiment.workspace + except: + return Workspace.from_config(path=self._luna_config["azureml"]["test_workspace_path"], + _file_name=self._luna_config["azureml"]["test_workspace_file_name"]) + + def RegisterModel(self, model_path, description, luna_python_model=None): + ws = self.GetAMLWorkspace() + + Model.register(model_path = model_path, + model_name = self._args.modelId, + description = description, + workspace = ws, + tags={'userId': self._args.userId, + 'productName': self._args.productName, + 'deploymentName': self._args.deploymentName, + 'apiVersion':self._args.apiVersion, + 'subscriptionId':self._args.subscriptionId, + 'modelId': self._args.modelId}) + + def GetDeploymentConfig(self, tags): + with open(self._luna_config['azureml']['workspace_config']) as file: + documents = yaml.full_load(file) + deployment_target = documents['deployment_target'] + if deployment_target == 'aks': + aks_cluster = documents['aks_cluster'] + + + with open(self._luna_config['deploy_config']) as file: + documents = yaml.full_load(file) + + if deployment_target == 'aci': + deployment_config = AciWebservice.deploy_configuration() + deployment_config.__dict__.update(documents['azureContainerInstance']) + deployment_config.dns_name_label = self._userInput["dns_name_label"] + elif deployment_target == 'aks': + deployment_config = AksWebservice.deploy_configuration() + deployment_config.__dict__.update(documents['kubernetes']) + deployment_config.compute_target_name = aks_cluster + deployment_config.namespace = self._userInput["dns_name_label"] + + deployment_config.tags = tags + return deployment_config + + def DeployModel(self): + + ws = self.GetAMLWorkspace() + model = Model(ws, self._args.modelId) + + print(model) + + myenv = Environment.from_conda_specification('scoring', self.luna_config['conda_env']) + + print(self.luna_config['code']['score']) + print(os.getcwd()) + + inference_config = InferenceConfig(entry_script=self.luna_config['code']['score'], source_directory = os.getcwd(), environment=myenv) + + deployment_config = self.GetDeploymentConfig( + tags={'userId': self._args.userId, + 'productName': self._args.productName, + 'deploymentName': self._args.deploymentName, + 'apiVersion':self._args.apiVersion, + 'subscriptionId':self._args.subscriptionId, + 'modelId': self._args.modelId}) + + service = Model.deploy(ws, self._args.endpointId, [model], inference_config, deployment_config) + service.wait_for_deployment(show_output = True) + + def DownloadModel(self, model_path=""): + ws = self.GetAMLWorkspace() + model = Model(ws, self._args.modelId) + full_model_path = os.path.join(os.getcwd(), model_path, "models/artifacts") + + os.makedirs(full_model_path, exist_ok=True) + model.download(target_dir = full_model_path, exist_ok=True) + return full_model_path diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/baseLunaUtils.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/baseLunaUtils.py new file mode 100644 index 0000000..44e4ee2 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/baseLunaUtils.py @@ -0,0 +1,58 @@ +from abc import ABCMeta, abstractmethod + +import yaml +import io +import argparse +import json + +class BaseLunaUtils(object): + + __metaclass__ = ABCMeta + + def ParseArguments(self): + return self._args, self._userInput + + def Init(self, luna_config, run_mode, args, userInput): + self._luna_config = luna_config + self._run_mode = run_mode + self._args= args + self._userInput = userInput + self._logger = None + + @property + def luna_config(self): + return self._luna_config + + @property + def run_mode(self): + return self.run_mode + + @property + def args(self): + return self._args + + @property + def user_input(self): + return self._userInput + + @property + def logger(self): + return self._logger + + @abstractmethod + def RegisterModel(self, model_path, description, luna_python_model=None): + """ + register model + """ + + @abstractmethod + def DeployModel(self): + """ + deploy model + """ + + @abstractmethod + def DownloadModel(self, model_path): + """ + download model + """ \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/localLunaUtils.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/localLunaUtils.py new file mode 100644 index 0000000..a2e87ea --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/localLunaUtils.py @@ -0,0 +1,23 @@ +from luna.baseLunaUtils import BaseLunaUtils +from luna.logging.localLunaLogger import LocalLunaLogger +import os + +class LocalLunaUtils(BaseLunaUtils): + + def Init(self, luna_config, run_mode, args, userInput): + super().Init(luna_config, run_mode, args, userInput) + self._logger = LocalLunaLogger() + + def RegisterModel(self, model_path, description, luna_python_model=None): + # do nothing + return + + def GetModelPath(self, context): + return os.getcwd() + + + def DeployModel(self): + return + + def DownloadModel(self, model_path="models"): + return os.path.join(os.getcwd(), model_path) \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/__init__.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/azuremlLunaLogger.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/azuremlLunaLogger.py new file mode 100644 index 0000000..576f153 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/azuremlLunaLogger.py @@ -0,0 +1,17 @@ +from luna.logging.baseLunaLogger import BaseLunaLogger +from azureml.core import Workspace, Run, Experiment + +class AzureMLLunaLogger(BaseLunaLogger): + + def log_metric(self, key, value): + run = Run.get_context(allow_offline=False) + run.log(key, value) + + def log_metrics(self, metrics): + run = Run.get_context(allow_offline=False) + for key in metrics: + run.log(key, metrics[key]) + + def upload_artifacts(self, local_path, upload_path): + run = Run.get_context(allow_offline=False) + run.upload_file(upload_path, local_path) \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/baseLunaLogger.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/baseLunaLogger.py new file mode 100644 index 0000000..890ab9d --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/baseLunaLogger.py @@ -0,0 +1,24 @@ +from abc import ABCMeta, abstractmethod + + +class BaseLunaLogger(object): + + __metaclass__ = ABCMeta + + @abstractmethod + def log_metric(self, key, value): + """ + log numeric metric + """ + + @abstractmethod + def log_metrics(self, metrics): + """ + log numeric metrics + """ + + @abstractmethod + def upload_artifacts(self, local_path, upload_path): + """ + upload artifacts (files) to the run + """ \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/localLunaLogger.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/localLunaLogger.py new file mode 100644 index 0000000..e1f5549 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/localLunaLogger.py @@ -0,0 +1,18 @@ +from luna.logging.baseLunaLogger import BaseLunaLogger + +class LocalLunaLogger(BaseLunaLogger): + + def log_metric(self, key, value): + """ + log numeric metric + """ + + def log_metrics(self, metrics): + """ + log numeric metrics + """ + + def upload_artifacts(self, local_path, upload_path): + """ + upload artifacts (files) to the run + """ \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/mlflowLunaLogger.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/mlflowLunaLogger.py new file mode 100644 index 0000000..9fc1ef9 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/logging/mlflowLunaLogger.py @@ -0,0 +1,18 @@ +from luna.logging.baseLunaLogger import BaseLunaLogger + +import mlflow +import os + +class MLFlowLunaLogger(BaseLunaLogger): + + def log_metric(self, key, value): + mlflow.log_metric(key, value) + + def log_metrics(self, metrics): + mlflow.log_metrics(metrics) + + def upload_artifacts(self, local_path, upload_path): + if os.path.isdir(local_path): + mlflow.log_artifacts(local_path, upload_path) + elif os.path.isfile(local_path): + mlflow.log_artifact(local_path, upload_path) \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/lunaClient.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/lunaClient.py new file mode 100644 index 0000000..4e5eeff --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/lunaClient.py @@ -0,0 +1,138 @@ +import yaml +import io +import argparse +import json +import os +import requests +import time + + +TRAINING_URL_FORMAT = "{base_url}/train?api-version={api_version}" +BATCHINFERENCE_URL_FORMAT = "{base_url}/models/{model_id}/batchinference?api-version={api_version}" +DEPLOY_URL_FORMAT = "{base_url}/models/{model_id}/deploy?api-version={api_version}" +GET_TRAINING_OP_URL_FORMAT = "{base_url}/operations/training/{model_id}?api-version={api_version}" +GET_INFERENCE_OP_URL_FORMAT = "{base_url}/operations/inference/{operation_id}?api-version={api_version}" +GET_DEPLOYMENT_OP_URL_FORMAT = "{base_url}/operations/deployment/{endpoint_id}?api-version={api_version}" + +GET_MODEL_URL_FORMAT = "{base_url}/models/{model_id}?api-version={api_version}" +GET_ENDPOINT_URL_FORMAT = "{base_url}/endpoints/{endpoint_id}?api-version={api_version}" + +class LunaClient(object): + + def __init__(self, base_url, key, api_version): + self._base_url = base_url + self._key = key + self._api_version = api_version + + + def get_request_header(self): + return {"Accept": "application/json", "Ocp-Apim-Subscription-Key": self._key} + + def train_model(self, user_input): + training_url = TRAINING_URL_FORMAT.format(base_url=self._base_url, api_version=self._api_version) + response = requests.post(training_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['modelId'] + + return None + + def get_training_operation(self, model_id): + train_op_url = GET_TRAINING_OP_URL_FORMAT.format(base_url = self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.get(train_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_batch_inference_operation(self, operation_id): + inference_op_url = GET_INFERENCE_OP_URL_FORMAT.format(base_url = self._base_url, operation_id=operation_id, api_version=self._api_version) + response = requests.get(inference_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_deployment_operation(self, endpoint_id): + inference_op_url = GET_DEPLOYMENT_OP_URL_FORMAT.format(base_url = self._base_url, endpoint_id=endpoint_id, api_version=self._api_version) + response = requests.get(inference_op_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + return None + + def get_model(self, model_id): + model_url = GET_MODEL_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.get(model_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + + def get_deployed_endpoint(self, endpoint_id): + model_url = GET_ENDPOINT_URL_FORMAT.format(base_url=self._base_url, endpoint_id=endpoint_id, api_version=self._api_version) + response = requests.get(model_url, headers=self.get_request_header()) + if response.status_code == 200: + return response.json() + + def wait_for_training_completion(self, model_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_training_operation(model_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + if op["status"] == 'Completed': + return self.get_model(model_id) + else: + return op + + def wait_for_batch_inference_completion(self, operation_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_batch_inference_operation(operation_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + return op + + def wait_for_deployment_completion(self, endpoint_id, timeout_is_seconds = 3600): + while True: + time.sleep(10) + op = self.get_deployment_operation(endpoint_id) + if op is None: + # TODO: what we gonna do here? + continue + if op["status"] != 'Running': + return op + + def batch_inference(self, model_id, user_input): + inference_url = BATCHINFERENCE_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + response = requests.post(inference_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['operationId'] + + return None + + def deploy_model(self, model_id, dns_name_label): + deploy_url = DEPLOY_URL_FORMAT.format(base_url=self._base_url, model_id=model_id, api_version=self._api_version) + user_input = {"dns_name_label": dns_name_label} + response = requests.post(deploy_url, headers=self.get_request_header(), data=json.dumps(user_input)) + + if response.status_code == 200: + return response.json()['endpointId'] + + return None + + def score_with_deployed_endpoint(self, endpoint_id, records, verboseMode=False): + endpoint = self.get_deployed_endpoint(endpoint_id) + header = {"Content-Type": "application/json", "Authorization": "Bearer "+endpoint["primaryKey"]} + + userInput = {"records": records, "verboseMode": verboseMode} + + response = requests.post(endpoint["scoringUrl"], headers=header, data=json.dumps(userInput)) + + return response.text + diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/lunaUtils.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/lunaUtils.py new file mode 100644 index 0000000..0df871e --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/lunaUtils.py @@ -0,0 +1,76 @@ +from abc import ABCMeta, abstractmethod +from luna.azuremlLunaUtils import AzureMLLunaUtils +from luna.mlflowLunaUtils import MLflowLunaUtils +from luna.localLunaUtils import LocalLunaUtils + +import yaml +import io +import argparse +import json +import os + +MLFLOW_MODEL_PATH = "MLFLOW_MODELS" + +class LunaUtils(object): + + @staticmethod + def Create(luna_config_file='luna_config.yml', run_mode='default', run_type='default', parameters={}): + with open(luna_config_file) as file: + luna_config = yaml.full_load(file) + + if run_type != 'default': + args, userInput = LunaUtils.ParseArgument(run_type) + else: + args = parameters + userInput = {} + + if run_mode == 'default': + run_mode = luna_config['run_mode'] + + if run_mode == 'azureml': + utils = AzureMLLunaUtils() + elif run_mode == 'mlflow': + utils = MLflowLunaUtils() + elif run_mode == 'local': + utils = LocalLunaUtils() + + utils.Init(luna_config, run_mode, args, userInput) + return utils + + @staticmethod + def ParseArgument(run_type): + + parser = argparse.ArgumentParser(run_type) + + parser.add_argument("--runMode", type=str, help="run mode") + parser.add_argument("--userInput", type=str, help="input data") + parser.add_argument("--modelId", type=str, help="model key") + + if run_type == 'inference' or run_type == 'batchinference': + parser.add_argument("--operationId", type=str, help="run id") + elif run_type == 'training' or run_type == 'train': + parser.add_argument("--userId", type=str, help="user id") + parser.add_argument("--productName", type=str, help="product name") + parser.add_argument("--deploymentName", type=str, help="deployment name") + parser.add_argument("--apiVersion", type=str, help="api version") + parser.add_argument("--subscriptionId", type=str, help="subscription id") + elif run_type == 'deployment' or run_type == 'deploy': + parser.add_argument("--userId", type=str, help="user id") + parser.add_argument("--productName", type=str, help="product name") + parser.add_argument("--deploymentName", type=str, help="deployment name") + parser.add_argument("--apiVersion", type=str, help="api version") + parser.add_argument("--subscriptionId", type=str, help="subscription id") + parser.add_argument("--endpointId", type=str, help="endpoint id") + + args = parser.parse_args() + userInput = json.loads(args.userInput) + return args, userInput + + @staticmethod + def GetModelPath(run_mode, context): + if run_mode == 'azureml': + return os.getenv('AZUREML_MODEL_DIR') + elif run_mode == 'mlflow': + return context[MLFLOW_MODEL_PATH] + else: + return 'models' \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/mlflowLunaUtils.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/mlflowLunaUtils.py new file mode 100644 index 0000000..013e91f --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/mlflowLunaUtils.py @@ -0,0 +1,58 @@ +from luna.baseLunaUtils import BaseLunaUtils +from mlflow.pyfunc import PythonModel, PythonModelContext +from mlflow.tracking.client import MlflowClient +from luna.logging.mlflowLunaLogger import MLFlowLunaLogger + +import mlflow +import os +import yaml + +MLFLOW_MODEL_PATH = "MLFLOW_MODELS" + +class MLflowLunaUtils(BaseLunaUtils): + + def Init(self, luna_config, run_mode, args, userInput): + super().Init(luna_config, run_mode, args, userInput) + mlflow.set_tracking_uri('databricks') + if not mlflow.active_run(): + with open(self._luna_config["mlflow"]["test_experiment"]) as file: + test_exp = yaml.full_load(file) + mlflow.start_run(experiment_id=test_exp["experiment_id"]) + + self._logger = MLFlowLunaLogger() + + def RegisterModel(self, model_path, description, luna_python_model=None): + mlFlowRun = mlflow.active_run() + if mlFlowRun: + tags={'userId': self._args.userId, + 'productName': self._args.productName, + 'deploymentName': self._args.deploymentName, + 'apiVersion':self._args.apiVersion, + 'subscriptionId':self._args.subscriptionId, + 'modelId': self._args.modelId} + mlflow.set_tags(tags) + mlflow.pyfunc.log_model(artifact_path=model_path, + python_model=luna_python_model, + artifacts={MLFLOW_MODEL_PATH: model_path}, + conda_env=self._luna_config["conda_env"]) + model_uri = "runs:/{run_id}/{artifact_path}".format(run_id=mlFlowRun.info.run_id, artifact_path=model_path) + mlflow.register_model( + model_uri, + self._args.modelId + ) + + def DeployModel(self): + return + + def DownloadModel(self, model_path=""): + currentRun = mlflow.active_run() + filter_string = 'tags."modelId" = "{model_id}"'.format(model_id = self._args.modelId) + client = MlflowClient(tracking_uri='databricks') + runs = client.search_runs(experiment_ids=currentRun.info.experiment_id, filter_string=filter_string) + + target_model_path = os.path.join(os.getcwd(), model_path) + + os.makedirs(target_model_path, exist_ok=True) + + full_model_path = client.download_artifacts(runs[0].info.run_id, "models/artifacts/", target_model_path) + return full_model_path \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/numpyJsonEncoder.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/numpyJsonEncoder.py new file mode 100644 index 0000000..ac6772f --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/numpyJsonEncoder.py @@ -0,0 +1,14 @@ +from json import JSONEncoder +import numpy as np +import json + +class NumpyJSONEncoder(JSONEncoder): + def default(self, obj): + if isinstance(obj, np.integer): + return int(obj) + elif isinstance(obj, np.floating): + return float(obj) + elif isinstance(obj, np.ndarray): + return obj.tolist() + else: + return super(NumpyJSONEncoder, self).default(obj) \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/utils.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/utils.py new file mode 100644 index 0000000..2e5fa63 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/luna/utils.py @@ -0,0 +1,203 @@ +from azureml.core import Workspace, Run, Experiment +from azureml.core.compute import ComputeTarget, AmlCompute +from azureml.core.environment import Environment +from azureml.core.model import Model, InferenceConfig +from azureml.core.runconfig import RunConfiguration +from azureml.core.webservice import AksWebservice, AciWebservice, Webservice +from azureml.pipeline.core.graph import PipelineParameter +from azureml.pipeline.steps import PythonScriptStep +from azureml.pipeline.core import Pipeline + +import yaml +import io +import argparse +import json +import os +import mlflow + + +def IfRunInAzureML(): + try: + run = Run.get_context(allow_offline=False) + print('runrun') + print(run) + except: + return False + + return run is not None + +def RegisterModel(model_path, description, args): + + if IfRunInAzureML(): + experiment = Run.get_context(allow_offline=False).experiment + ws = experiment.workspace + + Model.register(model_path = model_path, + model_name = args.modelId, + description = description, + workspace = ws, + tags={'userId': args.userId, + 'productName': args.productName, + 'deploymentName': args.deploymentName, + 'apiVersion':args.apiVersion, + 'subscriptionId':args.subscriptionId}) + else: + print('run on MLflow') + mlFlowRun = mlflow.active_run() + print(mlFlowRun) + if mlFlowRun: + mlflow.log_artifacts(model_path, model_path) + model_uri = "runs:/{run_id}/{artifact_path}".format(run_id=mlFlowRun.info.run_id, artifact_path=model_path) + print(model_uri) + result = mlflow.register_model( + model_uri, + args.modelId + ) + print(result) + + +def DownloadModel(model_id, model_path): + if IfRunInAzureML(): + experiment = Run.get_context(allow_offline=False).experiment + ws = experiment.workspace + model = Model(ws, model_id) + model.download(target_dir = model_path, exist_ok=True) + +def GetModelPath(): + # get model path if the model is deployed using AML + if os.getenv('AZUREML_MODEL_DIR'): + return os.getenv('AZUREML_MODEL_DIR') + +def ParseArguments(run_type): + parser = argparse.ArgumentParser(run_type) + + parser.add_argument("--runMode", type=str, help="run mode, support azureML or MLflow") + parser.add_argument("--userInput", type=str, help="input data") + parser.add_argument("--modelId", type=str, help="model key") + + if run_type == 'inference': + parser.add_argument("--operationId", type=str, help="run id") + else: + parser.add_argument("--userId", type=str, help="user id") + parser.add_argument("--productName", type=str, help="product name") + parser.add_argument("--deploymentName", type=str, help="deployment name") + parser.add_argument("--apiVersion", type=str, help="api version") + parser.add_argument("--subscriptionId", type=str, help="subscription id") + + if run_type == 'deployment': + parser.add_argument("--endpointId", type=str, help="endpoint id") + + args = parser.parse_args() + userInput = json.loads(args.userInput) + + return args, userInput + + +def Init(luna_config_file='luna_config.yml'): + with open(luna_config_file) as file: + luna_config = yaml.full_load(file) + return luna_config + +def GetDeploymentConfig(luna_config, dns_name_label,tags): + with open(luna_config['azureml']['workspace_config']) as file: + documents = yaml.full_load(file) + deployment_target = documents['deployment_target'] + aks_cluster = documents['aks_cluster'] + + + with open(luna_config['deploy_config']) as file: + documents = yaml.full_load(file) + + if deployment_target == 'aci': + deployment_config = AciWebservice.deploy_configuration() + deployment_config.__dict__.update(documents['azureContainerInstance']) + deployment_config.dns_name_label = dns_name_label + elif deployment_target == 'aks': + deployment_config = AksWebservice.deploy_configuration() + deployment_config.__dict__.update(documents['kubernetes']) + deployment_config.compute_target_name = aks_cluster + deployment_config.namespace = dns_name_label + + deployment_config.tags = tags + return deployment_config + +def DeployModel(): + + luna_config = Init() + + args, userInput = ParseArguments("deployment") + + dns_name_label = userInput["dns_name_label"] + model_id = args.modelId + endpoint_id = args.endpointId + + print(dns_name_label) + + ## If it is running in AML context + if IfRunInAzureML(): + experiment = Run.get_context(allow_offline=False).experiment + ws = experiment.workspace + model = Model(ws, model_id) + + print(model) + + myenv = Environment.from_conda_specification('scoring', luna_config['conda_env']) + + print(luna_config['code']['score']) + print(os.getcwd()) + + inference_config = InferenceConfig(entry_script=luna_config['code']['score'], source_directory = os.getcwd(), environment=myenv) + + deployment_config = GetDeploymentConfig( + luna_config, + dns_name_label, + tags={'userId': args.userId, + 'productName': args.productName, + 'deploymentName': args.deploymentName, + 'apiVersion':args.apiVersion, + 'subscriptionId':args.subscriptionId}) + + service = Model.deploy(ws, endpoint_id, [model], inference_config, deployment_config) + service.wait_for_deployment(show_output = True) + +def RunProject(azureml_workspace, entry_point, experiment_name, parameters, tags): + + luna_config = Init() + if azureml_workspace: + run_config = RunConfiguration.load(luna_config['azureml']['run_config']) + + arguments = GetPipelineArguments(luna_config['MLproject'], entry_point, parameters) + + trainStep = PythonScriptStep( + script_name=luna_config['code'][entry_point], + arguments=arguments, + inputs=[], + outputs=[], + source_directory=os.getcwd(), + runconfig=run_config, + allow_reuse=False + ) + + pipeline = Pipeline(workspace=azureml_workspace, steps=[trainStep]) + + experiment = Experiment(azureml_workspace, experiment_name) + pipeline_run = experiment.submit(pipeline, tags=tags) + return pipeline_run.id + + else: + return '00000000-0000-0000-0000-000000000000' + +def GetPipelineArguments(mlproject_file_path, run_type, parameters): + with open(mlproject_file_path) as file: + documents = yaml.full_load(file) + arguments = [] + for param in documents['entry_points'][run_type]['parameters']: + argumentName = '--' + param + arguments.append(argumentName) + # get input parameter values, set default value if not provided + if param in parameters: + pipelineParam = PipelineParameter(name=param, default_value=parameters[param]) + else: + pipelineParam = PipelineParameter(name=param, default_value=documents['entry_points'][run_type]['parameters'][param]['default']) + arguments.append(pipelineParam) + return arguments \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/readme.md b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/readme.md new file mode 100644 index 0000000..18b4567 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/readme.md @@ -0,0 +1,9 @@ +# Luna pubishing utils + +## ModelManager + +- **RegisterModel**: register a model +- **DownloadModel**: download a model +- **GetModelPath**: get the local model path +- **ParseArguments**: parse arguments +- **GetPipelineArguments**: get AML pipeline arguments \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/setup.py b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/setup.py new file mode 100644 index 0000000..49f39df --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Packages/luna-publish-utils/setup.py @@ -0,0 +1,30 @@ +import pathlib +from setuptools import setup + +# The directory containing this file +HERE = pathlib.Path(__file__).parent + +# The text of the README file +README = (HERE / "README.md").read_text() + +# This call to setup() does all the work +setup( + name="luna-publish-utils", + version="0.3.11", + description="Publishing utilities for project Luna", + long_description=README, + long_description_content_type="text/markdown", + url="https://github.com/allenwux/ml/src/luna/utils", + author="Xiaochen Wu", + author_email="allenwu.x@gmail.com", + license="MIT", + classifiers=[ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + ], + packages=["luna", "luna.logging"], + include_package_data=True, + install_requires=["azureml-sdk", "mlflow"], + entry_points={}, +) \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/CustomMeterEventService.cs b/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/CustomMeterEventService.cs index 85253c5..a9bcbee 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/CustomMeterEventService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/CustomMeterEventService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Clients; using Luna.Clients.Azure.Auth; using Luna.Clients.Azure.Storage; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/ICustomMeterEventService.cs b/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/ICustomMeterEventService.cs index 5a94606..7881a80 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/ICustomMeterEventService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/CustomMeterEvent/ICustomMeterEventService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Clients.Models.CustomMetering; using System; using System.Collections.Generic; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/AadSecretTmpService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/AadSecretTmpService.cs index be1903c..950cc28 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/AadSecretTmpService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/AadSecretTmpService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateArmTemplateParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateArmTemplateParameterService.cs index 9ef33f9..4a8d918 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateArmTemplateParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateArmTemplateParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Clients.Exceptions; using Luna.Data.Entities; using Luna.Data.Repository; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateParameterService.cs index 38a8e5b..7082370 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateService.cs index afb51fb..663c592 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ArmTemplateService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterDimensionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterDimensionService.cs index cdda8c3..d233ac5 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterDimensionService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterDimensionService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterService.cs index d89d8c5..fcdf975 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/CustomMeterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IAadSecretTmpService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IAadSecretTmpService.cs index 1cf7fc0..413e69a 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IAadSecretTmpService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IAadSecretTmpService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateArmTemplateParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateArmTemplateParameterService.cs index b9b8c83..27bf0d0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateArmTemplateParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateArmTemplateParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Data.Entities; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateParameterService.cs index 1f97390..efc8216 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateService.cs index 6400ba6..4d838f8 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IArmTemplateService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterDimensionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterDimensionService.cs index 883683e..50259ff 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterDimensionService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterDimensionService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterService.cs index 13da3cd..8e5ae45 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ICustomMeterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpAddressService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpAddressService.cs index 9c975ae..06b69ba 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpAddressService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpAddressService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpConfigService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpConfigService.cs index 464cd14..8dc9e99 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpConfigService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IIpConfigService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferParameterService.cs index fb6f9e5..6729928 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferService.cs index 74f6e52..8da7059 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IOfferService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IPlanService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IPlanService.cs index 2668603..74b1e37 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IPlanService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IPlanService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IRestrictedUserService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IRestrictedUserService.cs index 6d01078..3ded9fa 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IRestrictedUserService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IRestrictedUserService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionCustomMeterUsageService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionCustomMeterUsageService.cs index b1643d1..a34be59 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionCustomMeterUsageService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionCustomMeterUsageService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionParameterService.cs index 575544b..8d9c6f0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionService.cs index 31e1463..deaddc1 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ISubscriptionService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/ITelemetryDataConnectorService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/ITelemetryDataConnectorService.cs index d1467d4..0e1482c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/ITelemetryDataConnectorService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/ITelemetryDataConnectorService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookParameterService.cs index 4d97922..909c943 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookService.cs index c4cf2c0..c8bac5e 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Threading.Tasks; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookWebhookParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookWebhookParameterService.cs index 9e104f9..cea0775 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookWebhookParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IWebhookWebhookParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Data.Entities; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IpAddressService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IpAddressService.cs index 2085f17..ed02f42 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IpAddressService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IpAddressService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/IpConfigService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/IpConfigService.cs index 4a70832..d5837fb 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/IpConfigService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/IpConfigService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/AMLWorkspaceService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/AMLWorkspaceService.cs new file mode 100644 index 0000000..421a607 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/AMLWorkspaceService.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Luna.Clients.Azure.Auth; +using Luna.Clients.Controller; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.Entities; +using Luna.Data.Repository; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Luna.Clients.Azure.APIM; +using Luna.Services.Utilities.ExpressionEvaluation; + +namespace Luna.Services.Data.Luna.AI +{ + public class AMLWorkspaceService : IAMLWorkspaceService + { + private readonly ISqlDbContext _context; + private readonly ILogger _logger; + private readonly IKeyVaultHelper _keyVaultHelper; + private readonly IOptionsMonitor _options; + + /// + /// Constructor that uses dependency injection. + /// + /// The context to be injected. + /// The logger. + public AMLWorkspaceService(IOptionsMonitor options, + ISqlDbContext sqlDbContext, ILogger logger, IKeyVaultHelper keyVaultHelper) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _context = sqlDbContext ?? throw new ArgumentNullException(nameof(sqlDbContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _keyVaultHelper = keyVaultHelper; + } + + public async Task> GetAllAsync() + { + _logger.LogInformation(LoggingUtils.ComposeGetAllResourcesMessage(typeof(AMLWorkspace).Name)); + + // Get all products + var workspaces = await _context.AMLWorkspaces.ToListAsync(); + foreach (var workspace in workspaces) + { + workspace.AADApplicationSecrets = await _keyVaultHelper.GetSecretAsync(_options.CurrentValue.Config.VaultName, workspace.AADApplicationSecretName); + } + _logger.LogInformation(LoggingUtils.ComposeReturnCountMessage(typeof(AMLWorkspace).Name, workspaces.Count())); + + return workspaces; + } + + public async Task GetAsync(string workspaceName) + { + if (!await ExistsAsync(workspaceName)) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(Product).Name, + workspaceName)); + } + _logger.LogInformation(LoggingUtils.ComposeGetSingleResourceMessage(typeof(Product).Name, workspaceName)); + + // Get the product that matches the provided productName + var workspace = await _context.AMLWorkspaces.SingleOrDefaultAsync(o => (o.WorkspaceName == workspaceName)); + + workspace.AADApplicationSecrets = await _keyVaultHelper.GetSecretAsync(_options.CurrentValue.Config.VaultName, workspace.AADApplicationSecretName); + _logger.LogInformation(LoggingUtils.ComposeReturnValueMessage(typeof(Product).Name, + workspaceName, + JsonSerializer.Serialize(workspace))); + + return workspace; + } + + public async Task ExistsAsync(string workspaceName) + { + _logger.LogInformation(LoggingUtils.ComposeCheckResourceExistsMessage(typeof(AMLWorkspace).Name, workspaceName)); + + // Check that only one offer with this offerName exists and has not been deleted + var count = await _context.AMLWorkspaces + .CountAsync(p => (p.WorkspaceName == workspaceName)); + + // More than one instance of an object with the same name exists, this should not happen + if (count > 1) + { + throw new NotSupportedException(LoggingUtils.ComposeFoundDuplicatesErrorMessage(typeof(AMLWorkspace).Name, workspaceName)); + + } + else if (count == 0) + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(AMLWorkspace).Name, workspaceName, false)); + return false; + } + else + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(AMLWorkspace).Name, workspaceName, true)); + // count = 1 + return true; + } + } + + public async Task CreateAsync(AMLWorkspace workspace) + { + if (workspace is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(AMLWorkspace).Name), + UserErrorCode.PayloadNotProvided); + } + + // Check that an offer with the same name does not already exist + if (await ExistsAsync(workspace.WorkspaceName)) + { + throw new LunaConflictUserException(LoggingUtils.ComposeAlreadyExistsErrorMessage(typeof(AMLWorkspace).Name, + workspace.WorkspaceName)); + } + _logger.LogInformation(LoggingUtils.ComposeCreateResourceMessage(typeof(AMLWorkspace).Name, workspace.WorkspaceName, payload: JsonSerializer.Serialize(workspace))); + + workspace.Region = await ControllerHelper.GetRegion(workspace); + + // Add secret to keyvault + if (workspace.AADApplicationSecrets == null) + { + throw new LunaBadRequestUserException("AAD Application Secrets is needed with the aml workspace", UserErrorCode.AuthKeyNotProvided); + } + string secretName = $"amlkey-{Context.GetRandomString(12)}"; + await (_keyVaultHelper.SetSecretAsync(_options.CurrentValue.Config.VaultName, secretName, workspace.AADApplicationSecrets)); + + // Add workspace to db + workspace.AADApplicationSecretName = secretName; + _context.AMLWorkspaces.Add(workspace); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceCreatedMessage(typeof(AMLWorkspace).Name, workspace.WorkspaceName)); + + return workspace; + } + + public async Task UpdateAsync(string workspaceName, AMLWorkspace workspace) + { + if (workspace is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(AMLWorkspace).Name), + UserErrorCode.PayloadNotProvided); + } + _logger.LogInformation(LoggingUtils.ComposeUpdateResourceMessage(typeof(Product).Name, workspace.WorkspaceName, payload: JsonSerializer.Serialize(workspace))); + + // Get the offer that matches the offerName provided + var workspaceDb = await GetAsync(workspaceName); + + // Check if (the offerName has been updated) && + // (an offer with the same new name does not already exist) + if ((workspaceName != workspace.WorkspaceName) && (await ExistsAsync(workspace.WorkspaceName))) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(AMLWorkspace).Name), + UserErrorCode.NameMismatch); + } + + // Add secret to keyvault + if (workspace.AADApplicationSecrets == null) + { + throw new LunaBadRequestUserException("AAD Application Secrets is needed with the aml workspace", UserErrorCode.ArmTemplateNotProvided); + } + string secretName = string.IsNullOrEmpty(workspace.AADApplicationSecretName) ? $"amlkey-{Context.GetRandomString(12)}" : workspace.AADApplicationSecretName; + await (_keyVaultHelper.SetSecretAsync(_options.CurrentValue.Config.VaultName, secretName, workspace.AADApplicationSecrets)); + + // Copy over the changes + workspace.Region = await ControllerHelper.GetRegion(workspace); + workspaceDb.Copy(workspace); + + // Update workspaceDb values and save changes in db + _context.AMLWorkspaces.Update(workspaceDb); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(Product).Name, workspace.WorkspaceName)); + + return workspaceDb; + } + + public async Task DeleteAsync(string workspaceName) + { + _logger.LogInformation(LoggingUtils.ComposeDeleteResourceMessage(typeof(AMLWorkspace).Name, workspaceName)); + + var workspace = await GetAsync(workspaceName); + + // Delete secret from key vault + if (!string.IsNullOrEmpty(workspace.AADApplicationSecretName)) + { + string secretName = workspace.AADApplicationSecretName; + try + { + await (_keyVaultHelper.DeleteSecretAsync(_options.CurrentValue.Config.VaultName, secretName)); + } + catch { } + } + + // Remove the workspace from the db + _context.AMLWorkspaces.Remove(workspace); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceDeletedMessage(typeof(AMLWorkspace).Name, workspaceName)); + + return workspace; + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/APISubscriptionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/APISubscriptionService.cs new file mode 100644 index 0000000..bda0ed8 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/APISubscriptionService.cs @@ -0,0 +1,330 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Luna.Clients.Azure.APIM; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.Entities; +using Luna.Data.Repository; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Luna.Services.Data.Luna.AI +{ + public class APISubscriptionService : IAPISubscriptionService + { + private readonly ISqlDbContext _context; + private readonly IProductService _productService; + private readonly IDeploymentService _deploymentService; + private readonly ILogger _logger; + private readonly IAPISubscriptionAPIM _apiSubscriptionAPIM; + private readonly IUserAPIM _userAPIM; + + /// + /// Constructor that uses dependency injection. + /// + /// The context to be injected. + /// The service to be injected. + /// The service to be injected. + /// The logger. + /// The apim service. + /// The apim service. + public APISubscriptionService(ISqlDbContext sqlDbContext, IProductService productService, IDeploymentService deploymentService, + ILogger logger, IAPISubscriptionAPIM apiSubscriptionAPIM, IUserAPIM userAPIM) + { + _context = sqlDbContext ?? throw new ArgumentNullException(nameof(sqlDbContext)); + _productService = productService ?? throw new ArgumentNullException(nameof(productService)); + _deploymentService = deploymentService ?? throw new ArgumentNullException(nameof(deploymentService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _apiSubscriptionAPIM = apiSubscriptionAPIM ?? throw new ArgumentNullException(nameof(apiSubscriptionAPIM)); + _userAPIM = userAPIM ?? throw new ArgumentNullException(nameof(userAPIM)); + } + + /// + /// Gets all apiSubscriptions. + /// + /// The list of status of the apiSubscriptions. + /// The owner of the apiSubscriptions. + /// A list of all apiSubscriptions. + public async Task> GetAllAsync(string[] status = null, string owner = "") + { + _logger.LogInformation(LoggingUtils.ComposeGetAllResourcesMessage(typeof(APISubscription).Name)); + + // Get all apiSubscriptions + var subscriptionList = await _context.APISubscriptions.ToListAsync(); + + List apiSubscriptions = subscriptionList.Where(s => (status == null || status.Contains(s.Status)) && + (string.IsNullOrEmpty(owner) || s.UserId.Equals(owner, StringComparison.InvariantCultureIgnoreCase))).ToList(); + + + foreach (var apiSubscription in apiSubscriptions) + { + var deployment = await _context.Deployments.FindAsync(apiSubscription.DeploymentId); + var product = await _context.Products.FindAsync(deployment.ProductId); + + apiSubscription.ProductName = product.ProductName; + apiSubscription.DeploymentName = deployment.DeploymentName; + } + _logger.LogInformation(LoggingUtils.ComposeReturnCountMessage(typeof(APISubscription).Name, apiSubscriptions.Count())); + + return apiSubscriptions; + } + + /// + /// Get all active apiSubscription by Id + /// + /// The apiSubscription Id + /// The list of apiSubscription + public async Task GetAsync(Guid apiSubscriptionId) + { + _logger.LogInformation(LoggingUtils.ComposeGetSingleResourceMessage(typeof(APISubscription).Name, apiSubscriptionId.ToString())); + + // Find the apiSubscription that matches the subscriptionId provided + var apiSubscription = await _context.APISubscriptions.SingleOrDefaultAsync(o => (o.SubscriptionId.Equals(apiSubscriptionId))); + + // Check if apiSubscription exists + if (apiSubscription is null) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(APISubscription).Name, + apiSubscriptionId.ToString())); + } + + var deployment = await _context.Deployments.FindAsync(apiSubscription.DeploymentId); + // Check if deployment exists + if (deployment is null) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(APISubscription).Name, + apiSubscriptionId.ToString())); + } + apiSubscription.DeploymentName = deployment.DeploymentName; + + var product = await _context.Products.FindAsync(deployment.ProductId); + // Check if product exists + if (product is null) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(APISubscription).Name, + apiSubscriptionId.ToString())); + } + apiSubscription.ProductName = product.ProductName; + + _logger.LogInformation(LoggingUtils.ComposeReturnValueMessage(typeof(APISubscription).Name, + apiSubscriptionId.ToString(), + JsonSerializer.Serialize(apiSubscription))); + + return apiSubscription; + } + + /// + /// Creates a apiSubscription within a product and a deployment. + /// + /// The apiSubscription to create. + /// The created apiSubscription. + public async Task CreateAsync(APISubscription apiSubscription) + { + if (apiSubscription is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(APISubscription).Name), + UserErrorCode.PayloadNotProvided); + } + + // Check that an apiSubscription with the same Id does not already exist + if (await ExistsAsync(apiSubscription.SubscriptionId)) + { + throw new LunaConflictUserException(LoggingUtils.ComposeAlreadyExistsErrorMessage(typeof(APISubscription).Name, + apiSubscription.SubscriptionId.ToString())); + } + _logger.LogInformation(LoggingUtils.ComposeCreateResourceMessage(typeof(APISubscription).Name, apiSubscription.SubscriptionId.ToString(), payload: JsonSerializer.Serialize(apiSubscription))); + + apiSubscription.Status = "Subscribed"; + + if (apiSubscription.SubscriptionId == null) + { + apiSubscription.SubscriptionId = Guid.NewGuid(); + } + + apiSubscription.BaseUrl = _apiSubscriptionAPIM.GetBaseUrl(apiSubscription.ProductName, apiSubscription.DeploymentName); + + var deployment = await _deploymentService.GetAsync(apiSubscription.ProductName, apiSubscription.DeploymentName); + // Check if deployment exists + if (deployment is null) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(APISubscription).Name, + apiSubscription.SubscriptionId.ToString())); + } + apiSubscription.DeploymentId = deployment.Id; + apiSubscription.DeploymentName = deployment.DeploymentName; + + var product = await _productService.GetAsync(apiSubscription.ProductName); + // Check if product exists + if (product is null || product.Id != deployment.ProductId) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(APISubscription).Name, + apiSubscription.SubscriptionId.ToString())); + } + apiSubscription.ProductName = product.ProductName; + + // Update the apiSubscription created time + apiSubscription.CreatedTime = DateTime.UtcNow; + + // Update the apiSubscription last updated time + apiSubscription.LastUpdatedTime = apiSubscription.CreatedTime; + + // Add apiSubscription to APIM + await _userAPIM.CreateAsync(apiSubscription.UserId); + var apiSubscriptionAPIM = await _apiSubscriptionAPIM.CreateAsync(apiSubscription); + + // Update the apiSubscription primary key and secondary key + apiSubscription.PrimaryKey = apiSubscriptionAPIM.properties.primaryKey; + apiSubscription.SecondaryKey = apiSubscriptionAPIM.properties.secondaryKey; + + // Add apiSubscription to db + _context.APISubscriptions.Add(apiSubscription); + await _context._SaveChangesAsync(); + + _logger.LogInformation(LoggingUtils.ComposeResourceCreatedMessage(typeof(APISubscription).Name, apiSubscription.SubscriptionId.ToString())); + + return apiSubscription; + } + + /// + /// Updates a apiSubscription. + /// + /// The id of the apiSubscription to delete. + /// The updated apiSubscription. + /// The updated apiSubscriptionId. + public async Task UpdateAsync(Guid apiSubscriptionId, APISubscription apiSubscription) + { + if (apiSubscription is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(APISubscription).Name), + UserErrorCode.PayloadNotProvided); + } + _logger.LogInformation(LoggingUtils.ComposeUpdateResourceMessage(typeof(APISubscription).Name, apiSubscription.SubscriptionId.ToString(), payload: JsonSerializer.Serialize(apiSubscription))); + + // Get the apiSubscription that matches the apiSubscriptionId provided + var apiSubscriptionDb = await GetAsync(apiSubscriptionId); + if (!string.IsNullOrEmpty(apiSubscription.UserId) && !apiSubscriptionDb.UserId.Equals(apiSubscription.UserId, StringComparison.InvariantCultureIgnoreCase)) + { + throw new LunaBadRequestUserException("Owner name of an existing apiSubscription can not be changed.", UserErrorCode.InvalidParameter); + } + // Check if (the apiSubscription has been updated) && + // (an apiSubscription with the same new Id does not already exist) + if ((!apiSubscriptionId.Equals(apiSubscription.SubscriptionId)) && (await ExistsAsync(apiSubscription.SubscriptionId))) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(APISubscription).Name), + UserErrorCode.NameMismatch); + } + + // Copy over the changes + apiSubscriptionDb.Copy(apiSubscription); + + // Update the apiSubscription last updated time + apiSubscriptionDb.LastUpdatedTime = DateTime.UtcNow; + + // Update apiSubscription values and save changes in APIM + await _userAPIM.CreateAsync(apiSubscription.UserId); + var apiSubscriptionAPIM = await _apiSubscriptionAPIM.UpdateAsync(apiSubscriptionDb); + + // Update the apiSubscription primary key and secondry key + apiSubscriptionDb.PrimaryKey = apiSubscriptionAPIM.properties.primaryKey; + apiSubscriptionDb.SecondaryKey = apiSubscriptionAPIM.properties.secondaryKey; + + // Update apiSubscription values and save changes in db + _context.APISubscriptions.Update(apiSubscriptionDb); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(APISubscription).Name, apiSubscription.SubscriptionId.ToString())); + + return apiSubscriptionDb; + } + + /// + /// Delete a apiSubscription. + /// + /// The id of the apiSubscription to delete. + /// The subscription. + public async Task DeleteAsync(Guid apiSubscriptionId) + { + _logger.LogInformation(LoggingUtils.ComposeDeleteResourceMessage(typeof(Product).Name, apiSubscriptionId.ToString())); + + // Get the offer that matches the offerName provide + var apiSubscription = await GetAsync(apiSubscriptionId); + + // remove the product from the APIM + await _apiSubscriptionAPIM.DeleteAsync(apiSubscription); + + // Remove the product from the db + _context.APISubscriptions.Remove(apiSubscription); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceDeletedMessage(typeof(Product).Name, apiSubscriptionId.ToString())); + + return apiSubscription; + } + + /// + /// Regenerate key for the apiSubscription + /// + /// apiSubscription id + /// The key name + /// The apiSubscription with regenerated key + public async Task RegenerateKey(Guid apiSubscriptionId, string keyName) + { + if (!await ExistsAsync(apiSubscriptionId)) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(APISubscription).Name, + apiSubscriptionId.ToString())); + } + _logger.LogInformation(LoggingUtils.ComposeGetSingleResourceMessage(typeof(APISubscription).Name, apiSubscriptionId.ToString())); + + // Get the apiSubscription that matches the provided apiSubscriptionId + var apiSubscription = await _context.APISubscriptions.SingleOrDefaultAsync(o => (o.SubscriptionId.Equals(apiSubscriptionId))); + _logger.LogInformation(LoggingUtils.ComposeReturnValueMessage(typeof(APISubscription).Name, + apiSubscriptionId.ToString(), + JsonSerializer.Serialize(apiSubscription))); + + // Update apiSubscription primary key and secondary key and save changes in APIM + var apiSubscriptionProperties = await _apiSubscriptionAPIM.RegenerateKey(apiSubscriptionId, keyName); + apiSubscription.PrimaryKey = apiSubscriptionProperties.primaryKey; + apiSubscription.SecondaryKey = apiSubscriptionProperties.secondaryKey; + + // Update apiSubscription primary key and secondary key and save changes in db + _context.APISubscriptions.Update(apiSubscription); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(APISubscription).Name, apiSubscription.SubscriptionId.ToString())); + + return apiSubscription; + } + + public async Task ExistsAsync(Guid apiSubscriptionId) + { + _logger.LogInformation(LoggingUtils.ComposeCheckResourceExistsMessage(typeof(APISubscription).Name, apiSubscriptionId.ToString())); + + // Check that only one apiSubscription with this apiSubscriptionId exists and has not been deleted + var count = await _context.APISubscriptions + .CountAsync(s => (s.SubscriptionId.Equals(apiSubscriptionId))); + + // More than one instance of an object with the same name exists, this should not happen + if (count > 1) + { + throw new NotSupportedException(LoggingUtils.ComposeFoundDuplicatesErrorMessage(typeof(APISubscription).Name, apiSubscriptionId.ToString())); + + } + else if (count == 0) + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(APISubscription).Name, apiSubscriptionId.ToString(), false)); + return false; + } + else + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(Product).Name, apiSubscriptionId.ToString(), true)); + // count = 1 + return true; + } + } + + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/APIVersionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/APIVersionService.cs new file mode 100644 index 0000000..fbf0b6e --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/APIVersionService.cs @@ -0,0 +1,490 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Luna.Clients.Azure.APIM; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.Entities; +using Luna.Data.Repository; +using Luna.Clients.Azure.Auth; +using Luna.Services.Utilities.ExpressionEvaluation; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + + +namespace Luna.Services.Data.Luna.AI +{ + public class APIVersionService : IAPIVersionService + { + private readonly ISqlDbContext _context; + private readonly IProductService _productService; + private readonly IDeploymentService _deploymentService; + private readonly IAMLWorkspaceService _amlWorkspaceService; + private readonly ILogger _logger; + private readonly IAPIVersionAPIM _apiVersionAPIM; + private readonly IProductAPIVersionAPIM _productAPIVersionAPIM; + private readonly IOperationAPIM _operationAPIM; + private readonly IPolicyAPIM _policyAPIM; + private readonly IKeyVaultHelper _keyVaultHelper; + private readonly IOptionsMonitor _options; + + /// + /// Constructor that uses dependency injection. + /// + /// The context to be injected. + /// The service to be injected. + /// The service to be injected. + /// The logger. + /// The apim service. + /// The apim service. + /// The apim service. + /// The apim service. + public APIVersionService(ISqlDbContext sqlDbContext, IProductService productService, IDeploymentService deploymentService, IAMLWorkspaceService amlWorkspaceService, + ILogger logger, + IAPIVersionAPIM apiVersionAPIM, IProductAPIVersionAPIM productAPIVersionAPIM, IOperationAPIM operationAPIM, IPolicyAPIM policyAPIM, + IOptionsMonitor options, IKeyVaultHelper keyVaultHelper) + { + _context = sqlDbContext ?? throw new ArgumentNullException(nameof(sqlDbContext)); + _productService = productService ?? throw new ArgumentNullException(nameof(productService)); + _deploymentService = deploymentService ?? throw new ArgumentNullException(nameof(deploymentService)); + _amlWorkspaceService = amlWorkspaceService ?? throw new ArgumentNullException(nameof(amlWorkspaceService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _apiVersionAPIM = apiVersionAPIM ?? throw new ArgumentNullException(nameof(apiVersionAPIM)); + _productAPIVersionAPIM = productAPIVersionAPIM ?? throw new ArgumentNullException(nameof(productAPIVersionAPIM)); + _operationAPIM = operationAPIM ?? throw new ArgumentNullException(nameof(operationAPIM)); + _policyAPIM = policyAPIM ?? throw new ArgumentNullException(nameof(policyAPIM)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _keyVaultHelper = keyVaultHelper; + } + + private List GetOperationTypes(string productType) + { + switch (productType) + { + // Real Time Prediction + case "RTP": + return new List { + Clients.Models.Azure.OperationTypeEnum.RealTimePrediction, + }; + // Batch Inference + case "BI": + return new List{ + Clients.Models.Azure.OperationTypeEnum.BatchInferenceWithDefaultModel, + Clients.Models.Azure.OperationTypeEnum.GetABatchInferenceOperationWithDefaultModel, + Clients.Models.Azure.OperationTypeEnum.ListAllInferenceOperationsByUserWithDefaultModel, + }; + // Train Your Own Model + case "TYOM": + return new List{ + Clients.Models.Azure.OperationTypeEnum.TrainModel, + Clients.Models.Azure.OperationTypeEnum.ListAllTrainingOperationsByUser, + Clients.Models.Azure.OperationTypeEnum.GetATrainingOperationsByModelIdUser, + Clients.Models.Azure.OperationTypeEnum.GetAModelByModelIdUserProductDeployment, + Clients.Models.Azure.OperationTypeEnum.GetAllModelsByUserProductDeployment, + Clients.Models.Azure.OperationTypeEnum.DeleteAModel, + Clients.Models.Azure.OperationTypeEnum.BatchInference, + Clients.Models.Azure.OperationTypeEnum.GetABatchInferenceOperation, + Clients.Models.Azure.OperationTypeEnum.ListAllInferenceOperationsByUser, + Clients.Models.Azure.OperationTypeEnum.DeployRealTimePredictionEndpoint, + Clients.Models.Azure.OperationTypeEnum.GetADeployOperationByEndpointIdUser, + Clients.Models.Azure.OperationTypeEnum.ListAllDeployOperationsByUser, + Clients.Models.Azure.OperationTypeEnum.GetAllRealTimeServiceEndpointsByUserProductDeployment, + Clients.Models.Azure.OperationTypeEnum.GetARealTimeServiceEndpointByEndpointIdUserProductDeployment, + Clients.Models.Azure.OperationTypeEnum.DeleteAEndpoint + }; + default: + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(APIVersion).Name), + UserErrorCode.PayloadNotProvided); + + } + } + + private string GetUrl(AMLWorkspace workspace, string pipelineId) + { + return $"https://{workspace.Region}.api.azureml.ms/pipelines/v1.0" + workspace.ResourceId + $"/PipelineRuns/PipelineSubmit/{pipelineId}"; + } + + private APIVersion UpdateUrl(APIVersion version, AMLWorkspace workspace) + { + version.BatchInferenceAPI = string.IsNullOrEmpty(version.BatchInferenceId) ? "" : GetUrl(workspace, version.BatchInferenceId); + version.TrainModelAPI = string.IsNullOrEmpty(version.TrainModelId) ? "" : GetUrl(workspace, version.TrainModelId); + version.DeployModelAPI = string.IsNullOrEmpty(version.DeployModelId) ? "" : GetUrl(workspace, version.DeployModelId); + + version.BatchInferenceId = null; + version.TrainModelId = null; + version.DeployModelId = null; + + return version; + } + private string GetPipelineIdFromUrl(string pipelineId) + { + return pipelineId.Substring(pipelineId.LastIndexOf('/') + 1); + } + + private void SetPipelineIds(APIVersion version) + { + version.TrainModelId = string.IsNullOrEmpty(version.TrainModelAPI) ? null : GetPipelineIdFromUrl(version.TrainModelAPI); + version.BatchInferenceId = string.IsNullOrEmpty(version.BatchInferenceAPI) ? null : GetPipelineIdFromUrl(version.BatchInferenceAPI); + version.DeployModelId = string.IsNullOrEmpty(version.DeployModelAPI) ? null : GetPipelineIdFromUrl(version.DeployModelAPI); + } + + /// + /// Gets all apiVersions within a product and a deployment. + /// + /// The name of the product. + /// The name of the deployment. + /// A list of apiVersions. + public async Task> GetAllAsync(string productName, string deploymentName) + { + _logger.LogInformation(LoggingUtils.ComposeGetAllResourcesMessage(typeof(APIVersion).Name)); + + // Get the offer associated with the productName and deploymentName provided + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + var product = await _productService.GetAsync(productName); + + // Get all apiVersions with a FK to the deployment + var apiVersions = await _context.APIVersions.Where(v => v.DeploymentId == deployment.Id).ToListAsync(); + + foreach (var apiVersion in apiVersions) + { + apiVersion.DeploymentName = deployment.DeploymentName; + apiVersion.ProductName = product.ProductName; + + //check if there is amlworkspace + if (apiVersion.AMLWorkspaceId != 0) + { + // Get the amlWorkspace associated with the Id provided + var amlWorkspace = await _context.AMLWorkspaces.FindAsync(apiVersion.AMLWorkspaceId); + apiVersion.AMLWorkspaceName = amlWorkspace.WorkspaceName; + SetPipelineIds(apiVersion); + } + + if (!string.IsNullOrEmpty(apiVersion.AuthenticationKeySecretName)) + { + apiVersion.AuthenticationKey = await _keyVaultHelper.GetSecretAsync(_options.CurrentValue.Config.VaultName, apiVersion.AuthenticationKeySecretName); + } + + if (!string.IsNullOrEmpty(apiVersion.GitPersonalAccessTokenSecretName)) + { + apiVersion.GitPersonalAccessToken = await _keyVaultHelper.GetSecretAsync(_options.CurrentValue.Config.VaultName, apiVersion.GitPersonalAccessTokenSecretName); + } + } + + _logger.LogInformation(LoggingUtils.ComposeReturnCountMessage(typeof(APIVersion).Name, apiVersions.Count())); + + return apiVersions; + } + + /// + /// Gets an apiVersion within a product and a deployment. + /// + /// The name of the product. + /// The name of the deployment to get. + /// The name of the apiVersion to get. + /// The apiVersion. + public async Task GetAsync(string productName, string deploymentName, string versionName) + { + // Check that an apiVersion with the provided versionName exists within the given product and deployment + if (!(await ExistsAsync(productName, deploymentName, versionName))) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(APIVersion).Name, + versionName)); + } + _logger.LogInformation(LoggingUtils.ComposeGetSingleResourceMessage(typeof(APIVersion).Name, versionName)); + + // Get the deployment associated with the productName and deploymentName provided + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + + // Find the apiVersion that matches the productName and deploymentName provided + var apiVersion = await _context.APIVersions + .SingleOrDefaultAsync(v => (v.DeploymentId == deployment.Id) && (v.VersionName == versionName)); + apiVersion.DeploymentName = deployment.DeploymentName; + + // Get the product associated with the productName provided + var product = await _productService.GetAsync(productName); + apiVersion.ProductName = product.ProductName; + + //check if there is amlworkspace + if (apiVersion.AMLWorkspaceId != 0) + { + // Get the amlWorkspace associated with the Id provided + var amlWorkspace = await _context.AMLWorkspaces.FindAsync(apiVersion.AMLWorkspaceId); + apiVersion.AMLWorkspaceName = amlWorkspace.WorkspaceName; + SetPipelineIds(apiVersion); + } + + if (!string.IsNullOrEmpty(apiVersion.AuthenticationKeySecretName)) + { + apiVersion.AuthenticationKey = await _keyVaultHelper.GetSecretAsync(_options.CurrentValue.Config.VaultName, apiVersion.AuthenticationKeySecretName); + } + + if (!string.IsNullOrEmpty(apiVersion.GitPersonalAccessToken)) + { + apiVersion.GitPersonalAccessToken = await _keyVaultHelper.GetSecretAsync(_options.CurrentValue.Config.VaultName, apiVersion.GitPersonalAccessTokenSecretName); + } + + _logger.LogInformation(LoggingUtils.ComposeReturnValueMessage(typeof(APIVersion).Name, + versionName, + JsonSerializer.Serialize(apiVersion))); + + return apiVersion; + } + + /// + /// Creates an apiVersion within a product and a deployment. + /// + /// The name of the product. + /// The name of the deployment. + /// The apiVersion to create. + /// The created apiVersion. + public async Task CreateAsync(string productName, string deploymentName, APIVersion version) + { + if (version is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(APIVersion).Name), + UserErrorCode.PayloadNotProvided); + } + + // Check that the product and the deployment does not already have an apiVersion with the same versionName + if (await ExistsAsync(productName, deploymentName, version.VersionName)) + { + throw new LunaConflictUserException(LoggingUtils.ComposeAlreadyExistsErrorMessage(typeof(APIVersion).Name, + version.VersionName)); + } + + if (ExpressionEvaluationUtils.ReservedParameterNames.Contains(version.VersionName)) + { + throw new LunaConflictUserException($"Parameter {version.VersionName} is reserved. Please use a different name."); + } + _logger.LogInformation(LoggingUtils.ComposeCreateResourceMessage(typeof(APIVersion).Name, version.VersionName, payload: JsonSerializer.Serialize(version))); + + // Get the product associated with the productName provided + var product = await _productService.GetAsync(productName); + + // Get the deployment associated with the productName and the deploymentName provided + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + + // Set the FK to apiVersion + version.ProductName = product.ProductName; + version.DeploymentName = deployment.DeploymentName; + version.DeploymentId = deployment.Id; + + // Update the apiVersion created time + version.CreatedTime = DateTime.UtcNow; + + // Update the apiVersion last updated time + version.LastUpdatedTime = version.CreatedTime; + + //check if amlworkspace is required + if (!string.IsNullOrEmpty(version.AMLWorkspaceName)) + { + // Get the amlWorkspace associated with the AMLWorkspaceName provided + var amlWorkspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + version.AMLWorkspaceName = amlWorkspace.WorkspaceName; + version.AMLWorkspaceId = amlWorkspace.Id; + + // Update the apiVersion API + version = UpdateUrl(version, amlWorkspace); + } + + // add athentication key to keyVault if authentication type is key + if (version.AuthenticationType.Equals("Key", StringComparison.InvariantCultureIgnoreCase)) + { + if (version.AuthenticationKey == null) + { + throw new LunaBadRequestUserException("Authentication key is needed with the key authentication type", UserErrorCode.AuthKeyNotProvided); + } + string secretName = $"authkey-{Context.GetRandomString(12)}"; + await (_keyVaultHelper.SetSecretAsync(_options.CurrentValue.Config.VaultName, secretName, version.AuthenticationKey)); + version.AuthenticationKeySecretName = secretName; + } + + if (!string.IsNullOrEmpty(version.GitPersonalAccessToken)) + { + string secretName = $"gitpat-{Context.GetRandomString(12)}"; + await (_keyVaultHelper.SetSecretAsync(_options.CurrentValue.Config.VaultName, secretName, version.GitPersonalAccessToken)); + version.GitPersonalAccessTokenSecretName = secretName; + } + + // Add apiVersion to APIM + await _apiVersionAPIM.CreateAsync(version); + await _productAPIVersionAPIM.CreateAsync(version); + + var operationTypes = GetOperationTypes(product.ProductType); + foreach (var operationType in operationTypes) + { + var operation = _operationAPIM.GetOperation(operationType); + await _operationAPIM.CreateAsync(version, operation); + await _policyAPIM.CreateAsync(version, operation.name, operationType); + } + + // Add apiVersion to db + _context.APIVersions.Add(version); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceCreatedMessage(typeof(APIVersion).Name, version.VersionName)); + + return version; + } + + /// + /// Updates an apiVersion within a product and a deployment. + /// + /// The name of the product. + /// The name of the deployment to update. + /// The name of the apiVersion to update. + /// The updated apiVersion. + /// The updated apiVersion. + public async Task UpdateAsync(string productName, string deploymentName, string versionName, APIVersion version) + { + if (version is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(APIVersion).Name), + UserErrorCode.PayloadNotProvided); + } + + // Check if (the versionName has been updated) && + // (an APIVersion with the same new versionName does not already exist) + if ((versionName != version.VersionName) && (await ExistsAsync(productName, deploymentName, version.VersionName))) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(APIVersion).Name), + UserErrorCode.NameMismatch); + } + + _logger.LogInformation(LoggingUtils.ComposeUpdateResourceMessage(typeof(APIVersion).Name, versionName, payload: JsonSerializer.Serialize(version))); + + //check if amlworkspace is required + if (!string.IsNullOrEmpty(version.AMLWorkspaceName)) + { + // Get the amlWorkspace associated with the AMLWorkspaceName provided + var amlWorkspace = await _amlWorkspaceService.GetAsync(version.AMLWorkspaceName); + + // Update the apiVersion API + version = UpdateUrl(version, amlWorkspace); + } + + // add athentication key to keyVault if authentication type is key + if (version.AuthenticationType.Equals("Key", StringComparison.InvariantCultureIgnoreCase)) + { + if (version.AuthenticationKey == null) + { + throw new LunaBadRequestUserException("Authentication key is needed with the key authentication type", UserErrorCode.AuthKeyNotProvided); + } + string secretName = string.IsNullOrEmpty(version.AuthenticationKeySecretName) ? $"authkey-{Context.GetRandomString(12)}" : version.AuthenticationKeySecretName; + await (_keyVaultHelper.SetSecretAsync(_options.CurrentValue.Config.VaultName, secretName, version.AuthenticationKey)); + version.AuthenticationKeySecretName = secretName; + } + + if (!string.IsNullOrEmpty(version.GitPersonalAccessToken)) + { + string secretName = string.IsNullOrEmpty(version.GitPersonalAccessTokenSecretName) ? $"gitpat-{Context.GetRandomString(12)}" : version.GitPersonalAccessTokenSecretName; + await (_keyVaultHelper.SetSecretAsync(_options.CurrentValue.Config.VaultName, secretName, version.GitPersonalAccessToken)); + version.GitPersonalAccessTokenSecretName = secretName; + } + + // Get the apiVersion that matches the productName, deploymentName and versionName provided + var versionDb = await GetAsync(productName, deploymentName, versionName); + + // Copy over the changes + versionDb.Copy(version); + versionDb.LastUpdatedTime = DateTime.UtcNow; + + // Update version values and save changes in db + _context.APIVersions.Update(versionDb); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(APIVersion).Name, versionName)); + + return versionDb; + } + + /// + /// Deletes an apiVersion within a product and a deployment. + /// + /// The name of the product. + /// The name of the deployment. + /// The name of the apiVersion to delete. + /// The deleted apiVersion. + public async Task DeleteAsync(string productName, string deploymentName, string versionName) + { + _logger.LogInformation(LoggingUtils.ComposeDeleteResourceMessage(typeof(APIVersion).Name, versionName)); + + // Get the product that matches the productName provided + var product = await _productService.GetAsync(productName); + + // Get the apiVersion that matches the productName, the deploymentName and the versionName provided + var version = await GetAsync(productName, deploymentName, versionName); + version.ProductName = productName; + version.DeploymentName = deploymentName; + + // delete athentication key from keyVault if authentication type is key + if (version.AuthenticationType.Equals("Key", StringComparison.InvariantCultureIgnoreCase)) + { + if (version.AuthenticationKey != null) + { + string secretName = version.AuthenticationKeySecretName; + try + { + await (_keyVaultHelper.DeleteSecretAsync(_options.CurrentValue.Config.VaultName, secretName)); + } + catch + { + } + } + } + + // Remove the apiVersion from the APIM + await _apiVersionAPIM.DeleteAsync(version); + + // Remove the apiVersion from the db + _context.APIVersions.Remove(version); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceDeletedMessage(typeof(APIVersion).Name, versionName)); + + return version; + } + + /// + /// Checks if an apiVersion exists within a product and a deployment. + /// + /// The name of the product. + /// The name of the deployment to check exists. + /// The name of the apiVersion to check exists. + /// True if exists, false otherwise. + public async Task ExistsAsync(string productName, string deploymentName, string versionName) + { + _logger.LogInformation(LoggingUtils.ComposeCheckResourceExistsMessage(typeof(APIVersion).Name, versionName)); + + //Get the deployment associated with the productName and the deploymentName provided + var deployment = await _deploymentService.GetAsync(productName, deploymentName); + + // Check that only one apiVersion with this versionName exists within the deployment + var count = await _context.APIVersions + .CountAsync(a => (a.DeploymentId.Equals(deployment.Id)) && (a.VersionName == versionName)); + + // More than one instance of an object with the same name exists, this should not happen + if (count > 1) + { + throw new NotSupportedException(LoggingUtils.ComposeFoundDuplicatesErrorMessage(typeof(APIVersion).Name, + versionName)); + } + else if (count == 0) + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(APIVersion).Name, versionName, false)); + return false; + } + else + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(APIVersion).Name, versionName, true)); + // count = 1 + return true; + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/DeploymentService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/DeploymentService.cs new file mode 100644 index 0000000..81257fc --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/DeploymentService.cs @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Luna.Clients.Azure; +using Luna.Clients.Azure.APIM; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.Entities; +using Luna.Data.Repository; +using Luna.Services.Utilities.ExpressionEvaluation; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Luna.Services.Data.Luna.AI +{ + public class DeploymentService : IDeploymentService + { + private readonly ISqlDbContext _context; + private readonly IProductService _productService; + private readonly ILogger _logger; + private readonly IAPIVersionSetAPIM _apiVersionSetAPIM; + private readonly IAPIVersionAPIM _apiVersionAPIM; + + /// + /// Constructor that uses dependency injection. + /// + /// The context to be injected. + /// The service to be injected. + /// The logger. + /// The apim service. + /// The apim service. + public DeploymentService(ISqlDbContext sqlDbContext, IProductService productService, ILogger logger, + IAPIVersionSetAPIM apiVersionSetAPIM, IAPIVersionAPIM apiVersionAPIM) + { + _context = sqlDbContext ?? throw new ArgumentNullException(nameof(sqlDbContext)); + _productService = productService ?? throw new ArgumentNullException(nameof(productService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _apiVersionSetAPIM = apiVersionSetAPIM ?? throw new ArgumentNullException(nameof(apiVersionSetAPIM)); + _apiVersionAPIM = apiVersionAPIM ?? throw new ArgumentNullException(nameof(apiVersionAPIM)); + } + + /// + /// Gets all deployments within an product. + /// + /// The name of the product. + /// A list of deployments. + public async Task> GetAllAsync(string productName) + { + _logger.LogInformation(LoggingUtils.ComposeGetAllResourcesMessage(typeof(Deployment).Name)); + + // Get the product associated with the productName provided + var product = await _productService.GetAsync(productName); + + // Get all deployments with a FK to the product + var deployments = await _context.Deployments.Where(d => d.ProductId.Equals(product.Id)).ToListAsync(); + + foreach(var deployment in deployments) + { + deployment.ProductName = product.ProductName; + } + + _logger.LogInformation(LoggingUtils.ComposeReturnCountMessage(typeof(Deployment).Name, deployments.Count())); + + return deployments; + } + + /// + /// Gets an deployment within an product. + /// + /// The name of the product. + /// The name of the deployment to get. + /// The deployment. + public async Task GetAsync(string productName, string deploymentName) + { + // Check that an deployment with the provided deploymentName exists within the given product + if (!(await ExistsAsync(productName, deploymentName))) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(Deployment).Name, + deploymentName)); + } + _logger.LogInformation(LoggingUtils.ComposeGetSingleResourceMessage(typeof(Deployment).Name, deploymentName)); + + + // Get the product associated with the productName provided + var product = await _productService.GetAsync(productName); + + // Find the deployment that matches the deploymentName provided + var deployment = await _context.Deployments + .SingleOrDefaultAsync(a => (a.ProductId == product.Id) && (a.DeploymentName == deploymentName)); + + deployment.ProductName = product.ProductName; + _logger.LogInformation(LoggingUtils.ComposeReturnValueMessage(typeof(Deployment).Name, + deploymentName, + JsonSerializer.Serialize(deployment))); + + return deployment; + } + + /// + /// Creates an deployment within an product. + /// + /// The name of the product. + /// The deployment to create. + /// The created deployment. + public async Task CreateAsync(string productName, Deployment deployment) + { + if (deployment is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(Deployment).Name), + UserErrorCode.PayloadNotProvided); + } + + // Check that the deployment does not already have an DeploymentName with the same productName + if (await ExistsAsync(productName, deployment.DeploymentName)) + { + throw new LunaConflictUserException(LoggingUtils.ComposeAlreadyExistsErrorMessage(typeof(Deployment).Name, + deployment.DeploymentName)); + } + + if (ExpressionEvaluationUtils.ReservedParameterNames.Contains(deployment.DeploymentName)) + { + throw new LunaConflictUserException($"Parameter {deployment.DeploymentName} is reserved. Please use a different name."); + } + _logger.LogInformation(LoggingUtils.ComposeCreateResourceMessage(typeof(Deployment).Name, deployment.DeploymentName, payload: JsonSerializer.Serialize(deployment))); + + // Get the product associated with the productName provided + var product = await _productService.GetAsync(productName); + + // Set the FK to product + deployment.ProductId = product.Id; + deployment.ProductName = product.ProductName; + + // Update the deployment created time + deployment.CreatedTime = DateTime.UtcNow; + + // Update the deployment last updated time + deployment.LastUpdatedTime = deployment.CreatedTime; + + // Add deployment to APIM + await _apiVersionSetAPIM.CreateAsync(deployment); + await _apiVersionAPIM.CreateAsync(deployment); + + // Add deployment to db + _context.Deployments.Add(deployment); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceCreatedMessage(typeof(Deployment).Name, deployment.DeploymentName)); + + return deployment; + } + + /// + /// Updates an deployment within an product. + /// + /// The name of the product. + /// The name of the deployment to update. + /// The updated deployment. + /// The updated deployment. + public async Task UpdateAsync(string productName, string deploymentName, Deployment deployment) + { + if (deployment is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(Deployment).Name), + UserErrorCode.PayloadNotProvided); + } + + // Check if (the deploymentName has been updated) && + // (an deployment with the same new productName and deploymentName does not already exist) + if ((deploymentName != deployment.DeploymentName) && (await ExistsAsync(productName, deployment.DeploymentName))) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(Deployment).Name), + UserErrorCode.NameMismatch); + } + + _logger.LogInformation(LoggingUtils.ComposeUpdateResourceMessage(typeof(Deployment).Name, deploymentName, payload: JsonSerializer.Serialize(deployment))); + + // Get the deployment that matches the productName and deploymentName provided + var deploymentDB = await GetAsync(productName, deploymentName); + + // Copy over the changes + deploymentDB.Copy(deployment); + + // Update the deployment last updated time + deploymentDB.LastUpdatedTime = DateTime.UtcNow; + + // Update deployment values and save changes in APIM + await _apiVersionSetAPIM.UpdateAsync(deploymentDB); + await _apiVersionAPIM.UpdateAsync(deployment); + + // Update deployment values and save changes in db + _context.Deployments.Update(deploymentDB); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(Deployment).Name, deploymentName)); + + return deploymentDB; + } + + /// + /// Deletes an deployment within an product. + /// + /// The name of the product. + /// The name of the deployment to delete. + /// The deleted deployment. + public async Task DeleteAsync(string productName, string deploymentName) + { + _logger.LogInformation(LoggingUtils.ComposeDeleteResourceMessage(typeof(Deployment).Name, deploymentName)); + + // Get the deployment that matches the productName and deploymentName provided + var deployment = await GetAsync(productName, deploymentName); + + // Remove the deployment from the APIM + await _apiVersionAPIM.DeleteAsync(deployment); + + // Remove the deployment from the db + _context.Deployments.Remove(deployment); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceDeletedMessage(typeof(Deployment).Name, deploymentName)); + + return deployment; + } + + /// + /// Checks if an deployment exists within an product. + /// + /// The name of the product. + /// The name of the deployment to check exists. + /// True if exists, false otherwise. + public async Task ExistsAsync(string productName, string deploymentName) + { + _logger.LogInformation(LoggingUtils.ComposeCheckResourceExistsMessage(typeof(Deployment).Name, deploymentName)); + + //Get the product associated with the productName provided + var product = await _productService.GetAsync(productName); + + // Check that only one deployment with this deploymentName exists within the product + var count = await _context.Deployments + .CountAsync(d => (d.ProductId == product.Id) && (d.DeploymentName == deploymentName)); + + // More than one instance of an object with the same name exists, this should not happen + if (count > 1) + { + throw new NotSupportedException(LoggingUtils.ComposeFoundDuplicatesErrorMessage(typeof(Deployment).Name, + deploymentName)); + } + else if (count == 0) + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(Deployment).Name, deploymentName, false)); + return false; + } + else + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(Deployment).Name, deploymentName, true)); + // count = 1 + return true; + } + } + } +} + diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAMLWorkspaceService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAMLWorkspaceService.cs new file mode 100644 index 0000000..584630f --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAMLWorkspaceService.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Luna.Data.Entities; +using Luna.Data.DataContracts; + +namespace Luna.Services.Data +{ + /// + /// Interface that handles basic CRUD functionality for the workspace resource. + /// + public interface IAMLWorkspaceService + { + /// + /// Gets all workspaces. + /// + /// A list of workspaces. + Task> GetAllAsync(); + + /// + /// Gets an workspace by name. + /// + /// The name of the workspace to get. + /// The workspace. + Task GetAsync(string workspaceName); + + /// + /// Creates an workspace. + /// + /// The workspace to create. + /// The created workspace. + Task CreateAsync(AMLWorkspace workspace); + + /// + /// Updates an workspace. + /// + /// The name of the workspace to update. + /// The updated workspace. + /// The updated workspace. + Task UpdateAsync(string workspaceName, AMLWorkspace workspace); + + /// + /// Deletes an workspace. + /// + /// The name of the workspace to delete. + /// The deleted workspace. + Task DeleteAsync(string workspaceName); + + /// + /// Checks if an workspace exists. + /// + /// The name of the workspace to check exists. + /// True if exists, false otherwise. + Task ExistsAsync(string workspaceName); + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAPISubscriptionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAPISubscriptionService.cs new file mode 100644 index 0000000..d017add --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAPISubscriptionService.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Luna.Data.Entities; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Luna.Services.Data +{ + /// + /// Interface that handles basic CRUD functionality for the apiSubscription resource. + /// + public interface IAPISubscriptionService + { + /// + /// Gets all subscriptions. + /// + /// The list status of the subscription. + /// The owner of the subscription. + /// A list of all subsrciptions. + Task> GetAllAsync(string[] status = null, string owner = ""); + + /// + /// Gets an apiSubscription by name. + /// + /// The id of the apiSubscription to update. + /// The apiSubscription. + Task GetAsync(Guid apiSubscriptionId); + + /// + /// Creates an apiSubscription. + /// + /// The apiSubscription to create. + /// The created apiSubscription. + Task CreateAsync(APISubscription apiSubscription); + + /// + /// Updates an apiSubscription. + /// + /// The id of the apiSubscription to update. + /// The updated apiSubscription. + /// The updated apiSubscription. + Task UpdateAsync(Guid apiSubscriptionId, APISubscription apiSubscription); + + /// + /// Deletes an apiSubscription. + /// + /// The id of the apiSubscription to update. + /// The deleted apiSubscription. + Task DeleteAsync(Guid apiSubscriptionId); + + /// + /// Checks if an apiSubscription exists. + /// + /// The id of the apiSubscription to update. + /// True if exists, false otherwise. + Task ExistsAsync(Guid apiSubscriptionId); + + /// + /// Regenerate key for the subscription + /// + /// subscription id + /// The key name + /// The subscription with regenerated key + Task RegenerateKey(Guid apiSubscriptionId, string keyName); + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAPIVersionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAPIVersionService.cs new file mode 100644 index 0000000..e67bfe8 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IAPIVersionService.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Luna.Data.Entities; +using Luna.Data.DataContracts; + +namespace Luna.Services.Data +{ + /// + /// Interface that handles basic CRUD functionality for the version resource. + /// + public interface IAPIVersionService + { + /// + /// Gets all versions. + /// + /// The name of the product. + /// The name of the deployment + /// A list of versions. + Task> GetAllAsync(string productName, string deploymentName); + + /// + /// Gets an version by name. + /// + /// The name of the product. + /// The name of the deployment + /// The name of the version to get. + /// The version. + Task GetAsync(string productName, string deploymentName, string versionName); + + /// + /// Creates an version. + /// + /// The name of the product. + /// The name of the deployment + /// The version to create. + /// The created version. + Task CreateAsync(string productName, string deploymentName, APIVersion version); + + /// + /// Updates an version. + /// + /// The name of the product. + /// The name of the deployment + /// The name of the version to update. + /// The updated version. + /// The updated version. + Task UpdateAsync(string productName, string deploymentName, string versionName, APIVersion version); + + /// + /// Deletes an version. + /// + /// The name of the product. + /// The name of the deployment + /// The name of the version to delete. + /// The deleted version. + Task DeleteAsync(string productName, string deploymentName, string versionName); + + /// + /// Checks if an version exists. + /// + /// The name of the product. + /// The name of the deployment + /// The name of the version to check exists. + /// True if exists, false otherwise. + Task ExistsAsync(string productName, string deploymentName, string versionName); + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IDeploymentService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IDeploymentService.cs new file mode 100644 index 0000000..8049cfa --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IDeploymentService.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Luna.Data.Entities; +using Luna.Data.DataContracts; + +namespace Luna.Services.Data +{ + /// + /// Interface that handles basic CRUD functionality for the deployment resource. + /// + public interface IDeploymentService + { + /// + /// Gets all deployments. + /// + /// The name of the product. + /// A list of deployments. + Task> GetAllAsync(string productName); + + /// + /// Gets an deployment by name. + /// + /// The name of the product. + /// The name of the deployment to get. + /// The deployment. + Task GetAsync(string productName, string deploymentName); + + /// + /// Creates an deployment. + /// + /// The name of the product. + /// The deployment to create. + /// The created deployment. + Task CreateAsync(string productName, Deployment deployment); + + /// + /// Updates an deployment. + /// + /// The name of the product. + /// The name of the deployment to update. + /// The updated deployment. + /// The updated deployment. + Task UpdateAsync(string productName, string deploymentName, Deployment deployment); + + /// + /// Deletes an deployment. + /// + /// The name of the product. + /// The name of the deployment to delete. + /// The deleted deployment. + Task DeleteAsync(string productName, string deploymentName); + + /// + /// Checks if an deployment exists. + /// + /// The name of the product. + /// The name of the deployment to check exists. + /// True if exists, false otherwise. + Task ExistsAsync(string productName, string deploymentName); + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IProductService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IProductService.cs new file mode 100644 index 0000000..15aca05 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/IProductService.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Luna.Data.Entities; +using Luna.Data.DataContracts; + +namespace Luna.Services.Data +{ + /// + /// Interface that handles basic CRUD functionality for the product resource. + /// + public interface IProductService + { + /// + /// Gets all products. + /// + /// A list of products. + Task> GetAllAsync(); + + /// + /// Gets an product by name. + /// + /// The name of the product to get. + /// The product. + Task GetAsync(string productName); + + /// + /// Creates an product. + /// + /// The product to create. + /// The created product. + Task CreateAsync(Product product); + + /// + /// Updates an product. + /// + /// The name of the product to update. + /// The updated product. + /// The updated product. + Task UpdateAsync(string productName, Product product); + + /// + /// Deletes an product. + /// + /// The name of the product to delete. + /// The deleted product. + Task DeleteAsync(string productName); + + /// + /// Checks if an product exists. + /// + /// The name of the product to check exists. + /// True if exists, false otherwise. + Task ExistsAsync(string productName); + } +} \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/ProductService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/ProductService.cs new file mode 100644 index 0000000..dcc0968 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/Luna.AI/ProductService.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Luna.Clients.Azure.APIM; +using Luna.Clients.Exceptions; +using Luna.Clients.Logging; +using Luna.Data.Entities; +using Luna.Data.Enums; +using Luna.Data.Repository; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Luna.Services.Data.Luna.AI +{ + public class ProductService : IProductService + { + private readonly ISqlDbContext _context; + private readonly ILogger _logger; + private readonly IProductAPIM _productAPIM; + + /// + /// Constructor that uses dependency injection. + /// + /// The context to be injected. + /// The logger. + public ProductService(ISqlDbContext sqlDbContext, ILogger logger, + IProductAPIM productAPIM, IUserAPIM userAPIM) + { + _context = sqlDbContext ?? throw new ArgumentNullException(nameof(sqlDbContext)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _productAPIM = productAPIM ?? throw new ArgumentNullException(nameof(productAPIM)); + } + public async Task> GetAllAsync() + { + _logger.LogInformation(LoggingUtils.ComposeGetAllResourcesMessage(typeof(Product).Name)); + + // Get all products + var products = await _context.Products.ToListAsync(); + _logger.LogInformation(LoggingUtils.ComposeReturnCountMessage(typeof(Product).Name, products.Count())); + + return products; + } + + public async Task GetAsync(string productName) + { + if (!await ExistsAsync(productName)) + { + throw new LunaNotFoundUserException(LoggingUtils.ComposeNotFoundErrorMessage(typeof(Product).Name, + productName)); + } + _logger.LogInformation(LoggingUtils.ComposeGetSingleResourceMessage(typeof(Product).Name, productName)); + + // Get the product that matches the provided productName + var product = await _context.Products.SingleOrDefaultAsync(o => (o.ProductName == productName)); + _logger.LogInformation(LoggingUtils.ComposeReturnValueMessage(typeof(Product).Name, + productName, + JsonSerializer.Serialize(product))); + + return product; + } + + public async Task CreateAsync(Product product) + { + if (product is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(Product).Name), + UserErrorCode.PayloadNotProvided); + } + + // Check that an offer with the same name does not already exist + if (await ExistsAsync(product.ProductName)) + { + throw new LunaConflictUserException(LoggingUtils.ComposeAlreadyExistsErrorMessage(typeof(Product).Name, + product.ProductName)); + } + _logger.LogInformation(LoggingUtils.ComposeCreateResourceMessage(typeof(Product).Name, product.ProductName, payload: JsonSerializer.Serialize(product))); + + // Update the product created time + product.CreatedTime = DateTime.UtcNow; + + // Update the product last updated time + product.LastUpdatedTime = product.CreatedTime; + + await _productAPIM.CreateAsync(product); + + // Add product to db + _context.Products.Add(product); + await _context._SaveChangesAsync(); + _logger.LogInformation(LoggingUtils.ComposeResourceCreatedMessage(typeof(Offer).Name, product.ProductName)); + + return product; + } + + public async Task UpdateAsync(string productName, Product product) + { + if (product is null) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(Product).Name), + UserErrorCode.PayloadNotProvided); + } + _logger.LogInformation(LoggingUtils.ComposeUpdateResourceMessage(typeof(Product).Name, product.ProductName, payload: JsonSerializer.Serialize(product))); + + // Get the offer that matches the offerName provided + var productDb = await GetAsync(productName); + + // Check if (the offerName has been updated) && + // (an offer with the same new name does not already exist) + if ((productName != product.ProductName) && (await ExistsAsync(product.ProductName))) + { + throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(Product).Name), + UserErrorCode.NameMismatch); + } + + // Update the product owner + productDb.Owner = product.Owner; + + // Update the product last updated time + productDb.LastUpdatedTime = DateTime.UtcNow; + + // Update productDb values and save changes in APIM + await _productAPIM.UpdateAsync(productDb); + + // Update productDb values and save changes in db + _context.Products.Update(productDb); + await _context._SaveChangesAsync(); + + _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(Product).Name, product.ProductName)); + + return productDb; + } + + public async Task DeleteAsync(string productName) + { + _logger.LogInformation(LoggingUtils.ComposeDeleteResourceMessage(typeof(Product).Name, productName)); + + // Get the offer that matches the offerName provide + var product = await GetAsync(productName); + + // remove the product from the APIM + await _productAPIM.DeleteAsync(product); + + // Remove the product from the db + _context.Products.Remove(product); + await _context._SaveChangesAsync(); + + _logger.LogInformation(LoggingUtils.ComposeResourceDeletedMessage(typeof(Product).Name, productName)); + + return product; + } + + public async Task ExistsAsync(string productName) + { + _logger.LogInformation(LoggingUtils.ComposeCheckResourceExistsMessage(typeof(Product).Name, productName)); + + // Check that only one offer with this offerName exists and has not been deleted + var count = await _context.Products + .CountAsync(p => (p.ProductName == productName)); + + // More than one instance of an object with the same name exists, this should not happen + if (count > 1) + { + throw new NotSupportedException(LoggingUtils.ComposeFoundDuplicatesErrorMessage(typeof(Product).Name, productName)); + + } + else if (count == 0) + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(Product).Name, productName, false)); + return false; + } + else + { + _logger.LogInformation(LoggingUtils.ComposeResourceExistsOrNotMessage(typeof(Product).Name, productName, true)); + // count = 1 + return true; + } + } + } +} diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferParameterService.cs index c46f86d..07d64b2 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferService.cs index 300e436..380ef35 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/OfferService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/PlanService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/PlanService.cs index 2872595..a196162 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/PlanService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/PlanService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/RestrictedUserService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/RestrictedUserService.cs index 994814d..d703339 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/RestrictedUserService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/RestrictedUserService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionCustomMeterUsageService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionCustomMeterUsageService.cs index ce0d686..df123f5 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionCustomMeterUsageService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionCustomMeterUsageService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionParameterService.cs index b5621da..826e604 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionService.cs index 378f3a5..53fe77e 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/SubscriptionService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/TelemetryDataConnectorService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/TelemetryDataConnectorService.cs index 8d0fdce..29492f9 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/TelemetryDataConnectorService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/TelemetryDataConnectorService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookParameterService.cs index fbbe15a..2e526a0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookService.cs index 49f5913..1b47b15 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookWebhookParameterService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookWebhookParameterService.cs index e809cf7..c2ce003 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookWebhookParameterService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Data/WebhookWebhookParameterService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Clients.Exceptions; using Luna.Clients.Logging; using Luna.Data.Entities; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/IMarketplaceNotificationHandler.cs b/end-to-end-solutions/Luna/src/Luna.Services/IMarketplaceNotificationHandler.cs index d26b575..2c13976 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/IMarketplaceNotificationHandler.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/IMarketplaceNotificationHandler.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagemerError.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagemerError.cs index 6461b11..df90f1c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagemerError.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagemerError.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Services.Marketplace { public class FulfillmentManagementError diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManager.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManager.cs index b68c635..95b0fe7 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManager.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManager.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerException.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerException.cs index 82badc5..2a95a3c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerException.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Runtime.Serialization; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOperationResult.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOperationResult.cs index 82e0b78..a0757ce 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOperationResult.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOperationResult.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Collections.Generic; using System.Linq; using Luna.Clients.Models.Fulfillment; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOptions.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOptions.cs index c93f5ef..c9758fe 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOptions.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/FulfillmentManagerOptions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Luna.Clients; using Luna.Clients.Azure.Auth; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/IFulfillmentManager.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/IFulfillmentManager.cs index b07f030..8fdf267 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/IFulfillmentManager.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/IFulfillmentManager.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/OperationDetails.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/OperationDetails.cs index 1169acd..039146c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/OperationDetails.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/OperationDetails.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; namespace Luna.Services.Marketplace diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/ProvisioningHelper.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/ProvisioningHelper.cs index e000386..4b5c056 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/ProvisioningHelper.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/ProvisioningHelper.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.ComponentModel; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/SubscriptionAction.cs b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/SubscriptionAction.cs index 3127980..5d318c9 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/SubscriptionAction.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Marketplace/SubscriptionAction.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Services.Marketplace { public enum FulfillmentAction diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/IProvisioningService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/IProvisioningService.cs index 514d909..26471eb 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/IProvisioningService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/IProvisioningService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/InputStatesAttribute.cs b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/InputStatesAttribute.cs index a499d36..603fe28 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/InputStatesAttribute.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/InputStatesAttribute.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using Luna.Data.Enums; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/OutputStatesAttribute.cs b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/OutputStatesAttribute.cs index d32eede..08de56c 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/OutputStatesAttribute.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/OutputStatesAttribute.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using Luna.Data.Enums; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ProvisioningService.cs b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ProvisioningService.cs index 9c3b1ea..6b943ae 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ProvisioningService.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ProvisioningService.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Net; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ValidStatesAttribute.cs b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ValidStatesAttribute.cs index 55d041c..719f977 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ValidStatesAttribute.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Provisoning/ValidStatesAttribute.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using Luna.Data.Enums; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ClaimsIdentityExtensions.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ClaimsIdentityExtensions.cs index a17cd1f..f4e59ca 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ClaimsIdentityExtensions.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ClaimsIdentityExtensions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Security.Claims; using System.Security.Principal; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ContosoWebhookHandlerOptions.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ContosoWebhookHandlerOptions.cs index 288d20c..4372db8 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ContosoWebhookHandlerOptions.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ContosoWebhookHandlerOptions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Services.Utilities { public class ContosoWebhookHandlerOptions diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/Context.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/Context.cs index 35f7d3f..dbdf0c2 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/Context.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/Context.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Linq; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/ExpressionEvaluationUtils.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/ExpressionEvaluationUtils.cs index 2f8eb44..43eda51 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/ExpressionEvaluationUtils.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/ExpressionEvaluation/ExpressionEvaluationUtils.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections; using System.Collections.Generic; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MailOptions.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MailOptions.cs index 97b20e8..fd97f04 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MailOptions.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MailOptions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Services.Utilities { public class MailOptions diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MarketplaceSubscription.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MarketplaceSubscription.cs index a2efb4e..9d4d2b7 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MarketplaceSubscription.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/MarketplaceSubscription.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.ComponentModel.DataAnnotations; using Luna.Clients.Models.Fulfillment; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/NotificationModel.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/NotificationModel.cs index 86658f3..1e462ad 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/NotificationModel.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/NotificationModel.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using Luna.Services.WebHook; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/Options.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/Options.cs index 703c66c..41a1917 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/Options.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/Options.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Services.Utilities { public class DashboardOptions diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/StringExtensions.cs b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/StringExtensions.cs index 17e6f56..1c0fa86 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Utilities/StringExtensions.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Utilities/StringExtensions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Net.Mail; namespace Luna.Services.Utilities diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/IWebhookProcessor.cs b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/IWebhookProcessor.cs index de8cdfa..7597b79 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/IWebhookProcessor.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/IWebhookProcessor.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System.Threading; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookAction.cs b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookAction.cs index e0b6edc..6231ed0 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookAction.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookAction.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + namespace Luna.Services.WebHook { public enum WebhookAction diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookPayload.cs b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookPayload.cs index 0ab6845..41f7a6e 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookPayload.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookPayload.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; namespace Luna.Services.WebHook diff --git a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookProcessor.cs b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookProcessor.cs index e343daf..cbc9000 100644 --- a/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookProcessor.cs +++ b/end-to-end-solutions/Luna/src/Luna.Services/Webhook/WebhookProcessor.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Threading; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/package.json b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/package.json index 7761e32..971d9b4 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/package.json +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/package.json @@ -13,9 +13,11 @@ "formik": "^2.0.3", "json-to-url": "^1.0.5", "office-ui-fabric-react": "^6.174.0", + "query-string": "^6.9.0", "react": "^16.12.0", - "react-adal": "^0.5.0", "react-aad-msal": "^1.1.2", + "react-adal": "^0.5.0", + "react-copy-to-clipboard": "^5.0.2", "react-dom": "^16.11.0", "react-router-dom": "^5.0.1", "react-scripts": "^3.3.0", @@ -23,18 +25,17 @@ "react-toastify": "^5.4.1", "typescript": "3.6.4", "uuid": "^3.3.3", - "yup": "^0.27.0", - "query-string": "^6.9.0" + "yup": "^0.27.0" }, "devDependencies": { + "@types/react-adal": "^0.4.2", "@types/react-router-dom": "^4.3.5", "@types/uuid": "^3.4.6", - "@types/yup": "^0.26.23", - "@types/react-adal": "^0.4.2" + "@types/yup": "^0.26.23" }, "scripts": { "start": "PORT=44321 HTTPS=true react-scripts start", - "winstart": "set HTTPS=true&&set port=44321&&react-scripts start", + "winstart": "set HTTPS=true&&set port=44321&&react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/public/config.js b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/public/config.js index 939b5f6..78c08be 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/public/config.js +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/public/config.js @@ -1,7 +1,9 @@ var Configs = { - API_ENDPOINT: "https://lunatest-apiapp.azurewebsites.net/api/", + API_ENDPOINT: "https://lunaaitest-apiapp.azurewebsites.net/api/", //"https://lunatest-apiapp.azurewebsites.net/api/", // https://luna-dev-api.azurewebsites.net/api/ ISV_NAME: "Microsoft", - AAD_APPID: "50a6b69d-325a-4c02-aff3-51703d904789", - AAD_ENDPOINT: "https://lunatest-isvapp.azurewebsites.net", - HEADER_BACKGROUND_COLOR: "#118811" + AAD_APPID: "b9285d6f-f251-40c0-8123-7aa49ef61d0e", //"9ee174e4-e4ac-4a85-b6e3-85d52ca5f842", + AAD_ENDPOINT: "https://lunaaitest-isvapp.azurewebsites.net", + HEADER_BACKGROUND_COLOR: "#004578", + ENABLE_V1: "false", + ENABLE_V2: "true" } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/App.css b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/App.css index ccddf13..ad40617 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/App.css +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/App.css @@ -30,7 +30,7 @@ } .innercontainer { width: 100%; - margin: 0 auto; + margin: 0 auto; } .innercontainer .ms-Stack:first-child { @@ -80,7 +80,8 @@ div.ms-TextField > span div span { margin-bottom: 5px; } -.ms-Dropdown + div, .ms-Dropdown + .ms-layer + div { +.ms-Dropdown + div, +.ms-Dropdown + .ms-layer + div { background-color: #a80000; color: #fff; width: 98%; @@ -116,15 +117,15 @@ div.ms-TextField > span div span { font-size: 12px; } -.noborder.offer thead th{ -border-bottom: solid 1px var(--lightgraybg); +.noborder.offer thead th { + border-bottom: solid 1px var(--lightgraybg); } .noborder.offer td { /* height: 100px; */ vertical-align: top; text-align: left; } -.noborder.offer tbody tr{ +.noborder.offer tbody tr { line-height: 50px; } .ms-TextField .ms-TextField-field, @@ -135,7 +136,6 @@ border-bottom: solid 1px var(--lightgraybg); float: left; } - .errormessage { animation-name: css-1, css-14; animation-duration: 0.367s; @@ -441,11 +441,10 @@ table.offer thead tr th { height: 40px; line-height: 40px; } -.landingpagecontainner .headertitle{ +.landingpagecontainner .headertitle { padding: 0 20px 0px 20px; } -.button -{ +.button { background-color: #004578 !important; } .mainlanding { @@ -476,97 +475,82 @@ table.offer thead tr th { .mainlanding .ms-TextField .ms-TextField-fieldGroup { border-color: transparent; } -.mainlanding .graydropdown -{ +.mainlanding .graydropdown { color: #2288d8; } -.graydropdown span{ - background-color:#dedede; +.graydropdown span { + background-color: #dedede; color: #2288d8; } -.graydropdown .ms-Dropdown-caretDownWrapper -{ +.graydropdown .ms-Dropdown-caretDownWrapper { height: 30px; } -.graydropdown .ms-Dropdown-caretDownWrapper i -{ - color: #2288d8; +.graydropdown .ms-Dropdown-caretDownWrapper i { + color: #2288d8; } -.landing .App{ +.landing .App { height: 100%; margin: 0 auto; background-color: rgb(234, 234, 234); } -.landing .App div:last-child{ +.landing .App div:last-child { padding-top: 0; padding-bottom: 0; } -.landing .ms-Layer.root-133 .ms-Callout-main -{ +.landing .ms-Layer.root-133 .ms-Callout-main { background-color: #dedede; border: 1px solid #2288d8; } -.landing .ms-Layer.root-133 .ms-Callout-main .ms-Button-flexContainer -{ +.landing .ms-Layer.root-133 .ms-Callout-main .ms-Button-flexContainer { color: #2288d8; } -.subscriptiontitle -{ +.subscriptiontitle { width: 100%; text-align: left; line-height: 50px; border-bottom: 1px solid #eaeaea; } -.mainsubscriptionlist .css-67 -{ +.mainsubscriptionlist .css-67 { background-color: #efefef; } -.iconright -{ +.iconright { padding-left: 5px; float: right; } -.mainsubscriptionlist .ms-DetailsHeader-cellName -{ +.mainsubscriptionlist .ms-DetailsHeader-cellName { font-weight: normal; } -.updateplanmodal .ms-Dialog-main -{ +.updateplanmodal .ms-Dialog-main { max-width: 500px; } -.mainlanding tr:last-child td:last-child .ms-TextField-errorMessage span -{ +.mainlanding tr:last-child td:last-child .ms-TextField-errorMessage span { color: #fff; } -.disablecheckbox input -{ +.disablecheckbox input { position: absolute; - opacity: 0; - background: none; + opacity: 0; + background: none; } /* .updateplanmodal .ms-Dialog-inner .ms-Dialog-header { display: none; } */ /* Enduser*/ -.mainlanding .ms-ChoiceField -{ +.mainlanding .ms-ChoiceField { float: left; padding-right: 10%; } -.mainlanding .ms-ChoiceField-field::before -{ -margin-top: 5px; +.mainlanding .ms-ChoiceField-field::before { + margin-top: 5px; } -.mainlanding .ms-ChoiceField-field::after{ +.mainlanding .ms-ChoiceField-field::after { margin-top: 5px; } /*Loading css*/ -.loadingspinner .ms-Spinner-circle.ms-Spinner--large -{ +.loadingspinner .ms-Spinner-circle.ms-Spinner--large { width: 100px; height: 100px; } @@ -670,7 +654,50 @@ margin-top: 5px; } /*Loading css*/ -.ms-Layer -{ +.ms-Layer { z-index: 999 !important; +} + +.subv2ipinput { + width: 100%; + float: left; +} +.subv2ipinputw_90 { + width: 88%; +} +.subv2ipinputcopy { + display: flex; + width: 100%; + padding-left: 5px; + padding-right: 5px; +} +.copied { + color: #1885d8; + background-color: #d0d0d0; + border-radius: 5px; +} + +#keys .deleteicon { + font-size: 20px; + /* border:none !important; */ + /* display: contents!important; */ +} + +#subscriptionv2 .ms-TextField-fieldGroup +{ + border:none; +} +#subscriptionv2 .keyborder +{ + margin-right: 2%; +} + +#subscriptionv2 .keyborder .ms-TextField-fieldGroup +{ + border-width: 1px; + border-style: solid; + border-image: initial; + background: rgb(255, 255, 255); + border-radius: 2px; + border-color: rgb(138, 136, 134); } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/adalConfig.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/adalConfig.ts index 4e52d20..d4e4ed1 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/adalConfig.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/adalConfig.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {AdalConfig, adalGetToken, AuthenticationContext} from 'react-adal'; // Endpoint URL export const endpoint = window.Configs.AAD_ENDPOINT as string; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Content.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Content.tsx index a348b9c..772f1c0 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Content.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Content.tsx @@ -1,9 +1,6 @@ import React from 'react'; -import {Image, Stack, Text, Link, PrimaryButton, DefaultButton, getTheme, Nav, INavLink} from 'office-ui-fabric-react'; -import { useHistory, useLocation } from 'react-router'; -import { WebRoute } from "../shared/constants/routes"; -import { LayoutHelper, LayoutHelperMenuItem } from "./Layout"; -import { IOfferModel } from "../models"; +import { Stack} from 'office-ui-fabric-react'; + const Content: React.FunctionComponent = (props) => { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalErrorController.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalErrorController.tsx index dbdb438..86b45b2 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalErrorController.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalErrorController.tsx @@ -9,7 +9,7 @@ const GlobalErrorController: React.FC = () => { const [modalVisible, setModalVisible] = useState(false); - let errorState: IGlobalError | null = null; + //let errorState: IGlobalError | null = null; const globalContext = useGlobalContext(); @@ -20,6 +20,7 @@ const GlobalErrorController: React.FC = () => { useEffect(() => { + let errorState: IGlobalError | null = null; Hub.listen('ErrorChannel', (data) => { errorState = Cache.getItem( ERROR_STATE ); @@ -28,7 +29,7 @@ const GlobalErrorController: React.FC = () => { globalContext.showGlobalError(errorState); } }) - }, []); + }); useEffect(() => { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingController.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingController.tsx index 9520fe0..9cf2233 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingController.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingController.tsx @@ -6,6 +6,7 @@ import {GlobalProcessingModal} from './GlobalProcessingModal'; export const PROCESSING_STATE = 'processing_state'; const GlobalProcessingController: React.FC = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [processingVisible, setProcessingVisible] = useState(false); const globalContext = useGlobalContext(); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingModal.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingModal.tsx index 73424b5..d1de123 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingModal.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/GlobalProcessingModal.tsx @@ -1,8 +1,5 @@ import * as React from 'react'; -import { IGlobalError } from "../shared/components/GlobalProvider"; -import { ErrorModal } from '../shared/components/ErrorModal'; -import { Stack, Spinner, SpinnerSize, IStackProps } from 'office-ui-fabric-react'; - +import { Spinner, SpinnerSize, IStackProps } from 'office-ui-fabric-react'; interface ParentProps { processingVisible: boolean; @@ -15,7 +12,9 @@ export const GlobalProcessingModal: React.SFC = (props) => { const { processingVisible } = props; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const rowProps: IStackProps = { horizontal: true, verticalAlign: 'center' }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const tokens = { sectionStack: { childrenGap: 10 diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Header.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Header.tsx index 23bce86..bbe4cf5 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Header.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/Header.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Image, Link, Stack, Text, getTheme} from 'office-ui-fabric-react'; +import {Image, Link, Stack, getTheme} from 'office-ui-fabric-react'; import {useHistory} from "react-router"; import {WebRoute} from "../shared/constants/routes"; import adalContext from "../adalConfig"; @@ -13,6 +13,7 @@ const Header: React.FunctionComponent = () => { const headerBackgroundColor = window.Configs.HEADER_BACKGROUND_COLOR; let userName = ""; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const theme = getTheme(); const history = useHistory(); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/SubscriptionContent.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/SubscriptionContent.tsx index 41a8498..7427826 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/SubscriptionContent.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/layout/SubscriptionContent.tsx @@ -2,11 +2,8 @@ import React, {useEffect, useState} from 'react'; import {getTheme, PrimaryButton, Stack} from 'office-ui-fabric-react'; import {useHistory, useLocation} from 'react-router'; -import {WebRoute} from "../shared/constants/routes"; -import {LayoutHelper, LayoutHelperMenuItem} from "./Layout"; import SubscriptionsService from "../services/SubscriptionsService"; -import {IOfferModel, ISubscriptionsModel} from "../models"; -import AlternateButton from "../shared/components/AlternateButton"; +import {ISubscriptionsModel} from "../models"; import {useGlobalContext} from "../shared/components/GlobalProvider"; type SubscriptionProps = { @@ -17,14 +14,18 @@ const SubscriptionContent: React.FunctionComponent = (props) const {subscriptionId} = props; + // eslint-disable-next-line @typescript-eslint/no-unused-vars let title = 'New'; if (subscriptionId) { title = subscriptionId; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const logo = "https://mondrian.mashable.com/uploads%252Fcard%252Fimage%252F918220%252F316bce31-4c38-4f3b-b743-a17406175286.png%252F950x534__filters%253Aquality%252880%2529.png?signature=ASyPwdNVsAIo5E7uzfpoydo-rmc=&source=https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com"; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const isvName = "Slack"; const history = useHistory(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const location = useLocation(); const theme = getTheme(); @@ -80,18 +81,19 @@ const SubscriptionContent: React.FunctionComponent = (props) activatedBy: '', }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const globalContext = useGlobalContext(); useEffect( () => { if (subscriptionId) getSubscriptionInfo(subscriptionId); - },[]); + }, []); - const handleFormSubmission = async(e) => { - if (globalContext.saveForm) - await globalContext.saveForm(); - }; + // const handleFormSubmission = async(e) => { + // if (globalContext.saveForm) + // await globalContext.saveForm(); + // }; const handleBackButton = () => { history.push(`/Subscriptions`); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IBaseModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IBaseModel.ts index 21b4a4c..a8a6dcb 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IBaseModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IBaseModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export interface IBaseModel { isNew?: boolean; isDeleted?: boolean; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IIpConfigModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IIpConfigModel.ts index 078cc5b..1333759 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IIpConfigModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IIpConfigModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {IBaseModel} from "./IBaseModel"; export interface IIpConfigModel extends IBaseModel { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferModel.ts index c61a43e..a5d73bf 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {IBaseModel} from "./IBaseModel"; export interface IOfferModel extends IBaseModel { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferParameterModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferParameterModel.ts index 1887137..970aab6 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferParameterModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferParameterModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {IBaseModel} from "./IBaseModel"; export interface IOfferParameterModel extends IBaseModel { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferWarningsModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferWarningsModel.ts index 24598bb..f57535c 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferWarningsModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/IOfferWarningsModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export interface IOfferWarningsModel { warnings: string[]; } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/ISubscriptionsModel.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/ISubscriptionsModel.tsx index 3d1a031..a295d50 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/ISubscriptionsModel.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/ISubscriptionsModel.tsx @@ -1,6 +1,3 @@ -import { IBaseModel } from "./IBaseModel"; -import { IDropdownOption } from "office-ui-fabric-react"; - export interface IParamModel { name: string, type: string, @@ -90,4 +87,22 @@ export interface IOperationHistoryModel { requestId: string, statusCode: number, success: boolean +} + +export interface ISubscriptionsV2Model { + subscriptionId: string, + subscriptionName: string, + userId: string, + productName: string, + deploymentName: string, + status: string, + baseUrl: string, + primaryKey: string, + secondaryKey: string + +} + +export interface ISubscriptionsV2RefreshKeyModel { + subscriptionId: string, + keyName:string; } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/Result.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/Result.ts index e0bb070..b008f6e 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/Result.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/Result.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export interface IError { [key: string] : string[] diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/index.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/index.ts index 4ca5e75..2f970ef 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/index.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/models/index.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export * from './IARMTemplateParameterModel'; export * from './IARMTemplateModel'; export * from './IIpConfigModel'; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/react-app-env.d.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/react-app-env.d.ts index 6431bc5..dbc18b2 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/react-app-env.d.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/react-app-env.d.ts @@ -1 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /// diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/formutlis/landingUtils.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/formutlis/landingUtils.ts index 0164350..1d64a2d 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/formutlis/landingUtils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/formutlis/landingUtils.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {ILandingModel} from "../../../models/IEnduserLandingModel"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/landingPage.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/landingPage.tsx index 8254520..e5d43a9 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/landingPage.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/EndUser/landingPage.tsx @@ -18,7 +18,7 @@ import { ILandingModel, IParameterModel } from '../../models/IEnduserLandingMode import { Loading } from '../../shared/components/Loading'; import EndUserLandingService from "../../services/EndUserLandingService"; import OfferParameterService from "../../services/OfferParameterService"; -import { IOfferParameterModel, ISubscriptionsModel, Result } from '../../models'; +import { IOfferParameterModel, ISubscriptionsModel } from '../../models'; import SubscriptionsService from '../../services/SubscriptionsService'; import { useHistory, useLocation } from 'react-router'; import { toast } from "react-toastify"; @@ -69,19 +69,19 @@ const LandingPage: React.FunctionComponent = (props) => { console.log('will unmount'); body.classList.remove('landing'); } - }, []) + }) - const handledSubmissionErrors = (result: Result, setSubmitting: any): boolean => { - if (!result.success) { - if (result.hasErrors) - // TODO - display the errors here - toast.error(result.errors.join(', ')); - setSubmitting(false); - return true; - } - return false; - } + // const handledSubmissionErrors = (result: Result, setSubmitting: any): boolean => { + // if (!result.success) { + // if (result.hasErrors) + // // TODO - display the errors here + // toast.error(result.errors.join(', ')); + // setSubmitting(false); + // return true; + // } + // return false; + // } const getinfo = async () => { setLoadingFormData(true); @@ -127,7 +127,7 @@ const LandingPage: React.FunctionComponent = (props) => { // redirect to the subscription list because the user already has the subscription if ((subscriptionResponse.value && subscriptionResponse.success && (subscriptionResponse.value as ISubscriptionsModel[]) - && (subscriptionResponse.value as ISubscriptionsModel[]).findIndex(x => x.subscriptionId == formData.subscriptionId) >= 0) + && (subscriptionResponse.value as ISubscriptionsModel[]).findIndex(x => x.subscriptionId === formData.subscriptionId) >= 0) || !offerParametersResponse.success) { history.push("/Subscriptions"); return; @@ -139,6 +139,7 @@ const LandingPage: React.FunctionComponent = (props) => { var offerParameters = offerParametersResponse.value as IOfferParameterModel[]; let Parametersarray: IParameterModel[] = []; offerParameters.map((item, index) => { + return ( Parametersarray.push({ parameterName: item.parameterName, displayName: item.displayName, @@ -148,7 +149,7 @@ const LandingPage: React.FunctionComponent = (props) => { valueList: item.valueList, maximum: item.maximum, minimum: item.minimum - }); + })) }); formData.inputParameters = Parametersarray; } @@ -193,11 +194,12 @@ const LandingPage: React.FunctionComponent = (props) => { const dropDownValues = (items: string): IDropdownOption[] => { let listitems: IDropdownOption[] = []; items ? items.split(';').map((value, index) => { + return ( listitems.push( { key: value, text: value - }) + })) }) : listitems.push( { @@ -228,6 +230,7 @@ const LandingPage: React.FunctionComponent = (props) => { return new Date(Parameter.dob) } else { let currentDate = new Date(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars let key = ((currentDate.getMonth() + 1) + '/' + currentDate.getDate() + '/' + (currentDate.getFullYear() % 100)); //setFieldValue(fieldKey, key, true); } @@ -235,8 +238,8 @@ const LandingPage: React.FunctionComponent = (props) => { }; const renderControls = (Parameter: IParameterModel, idx: number, handleChange, handleBlur, setFieldValue, touched) => { - if (Parameter.valueType == 'string') { - if (Parameter.valueList.length == 0) { + if (Parameter.valueType === 'string') { + if (Parameter.valueList.length === 0) { return ( { }} />) } - } else if (Parameter.valueType == 'number') { - if (Parameter.valueList.length == 0) { + } else if (Parameter.valueType === 'number') { + if (Parameter.valueList.length === 0) { if (Parameter.maximum && Parameter.maximum > 0) { return ( { }} />) } - } else if (Parameter.valueType == 'datetime') { + } else if (Parameter.valueType === 'datetime') { return ( @@ -307,7 +310,7 @@ const LandingPage: React.FunctionComponent = (props) => { ) } - else if (Parameter.valueType == 'boolean') { + else if (Parameter.valueType === 'boolean') { return ( @@ -391,7 +394,7 @@ const LandingPage: React.FunctionComponent = (props) => { : - !formState || !formState.planName || formState.planName.length == 0 ? + !formState || !formState.planName || formState.planName.length === 0 ? Invalid Token : { console.log('rendering items'); console.log('param values: ', input.parameterValues); - input.inputParameters.map((item, index) => { - + input.inputParameters.map((item, index) => { if (item.valueType === 'number') { + return ( subscriptionsModel.InputParameters.push( { name: item.parameterName, type: item.valueType, value: '"'+parseInt(input.parameterValues[index][item.parameterName])+'"' - }) + })) } else{ + return ( subscriptionsModel.InputParameters.push( { name: item.parameterName, type: item.valueType, value: input.parameterValues[index][item.parameterName] - }) + })) } - - }) let createSubscriptionsResult = await SubscriptionsService.create(subscriptionsModel); @@ -457,8 +459,7 @@ const LandingPage: React.FunctionComponent = (props) => { {({ isSubmitting, setFieldValue, values, handleChange, handleBlur, touched, errors }) => { console.log('values: ' + JSON.stringify(values)); return ( -
-

+ {formError && {{ formError }} } diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/OperationHistory.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/OperationHistory.tsx index 4a906d3..ce407e7 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/OperationHistory.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/OperationHistory.tsx @@ -29,6 +29,7 @@ function formatDate(datetime) { } const OperationHistory: React.FunctionComponent = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const globalContext = useGlobalContext(); const [state, setstate] = useState(getInitialOperationHistoryModel); const [loadingOperationHistory, setLoadingOperationHistory] = useState(true); @@ -38,7 +39,7 @@ const OperationHistory: React.FunctionComponent = () => { useEffect(() => { if (subscriptionId) getData(subscriptionId); - }, []); + }, [subscriptionId]); const getData = async (subscriptionId: string) => { setLoadingOperationHistory(true); @@ -120,6 +121,7 @@ export type IOperationHistoryProps = { formError?: string | null; } export const OperationHistoryList: React.FunctionComponent = (props) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const {isSubmitting, setFieldValue, values, handleChange, handleBlur, touched, errors, resetForm, handleSubmit, submitForm} = useFormikContext(); // formikProps const globalContext = useGlobalContext(); @@ -127,10 +129,10 @@ export const OperationHistoryList: React.FunctionComponent { await submitForm(); }); - }, []); + },[]); const OperationHistory = ({data}) => { - if (!data || data.length == 0) { + if (!data || data.length === 0) { return No History ; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/Subscriptions.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/Subscriptions.tsx index c7b94de..bcf9e74 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/Subscriptions.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/Subscriptions.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, {useEffect, useState} from 'react'; import { DetailsList, DetailsListLayoutMode, @@ -15,42 +15,74 @@ import { Stack, TextField } from 'office-ui-fabric-react'; -import { useHistory } from "react-router"; -import { IUpdateSubscriptionModel, Result } from '../../models'; -import { Loading } from "../../shared/components/Loading"; +import {useHistory} from "react-router"; +import {IError, IUpdateSubscriptionModel} from '../../models'; +import {Loading} from "../../shared/components/Loading"; import AlternateButton from "../../shared/components/AlternateButton"; -import { Formik } from "formik"; -import { ISubscriptionsModel } from '../../models/ISubscriptionsModel'; +import {Formik} from "formik"; +import { + ISubscriptionsModel, + ISubscriptionsV2Model, + ISubscriptionsV2RefreshKeyModel +} from '../../models/ISubscriptionsModel'; import SubscriptionsService from '../../services/SubscriptionsService'; -import { toast } from "react-toastify"; +import {toast} from "react-toastify"; import PlansService from '../../services/PlansService'; -import { getInitialUpdateSubscriptionModel, subscriptionValidator } from "./formUtils/subscriptionFormUtils"; +import { + getInitialSubscriptionV2, + getInitialUpdateSubscriptionModel, + subscriptionValidator +} from "./formUtils/subscriptionFormUtils"; import adalContext from "../../adalConfig"; -import { useGlobalContext } from '../../shared/components/GlobalProvider'; -import { handleSubmissionErrorsForForm } from '../../shared/formUtils/utils'; +import {useGlobalContext} from '../../shared/components/GlobalProvider'; +import {handleSubmissionErrorsForForm} from '../../shared/formUtils/utils'; +import FormLabel from '../../shared/components/FormLabel'; +import {CopyToClipboard} from 'react-copy-to-clipboard'; +import { SubscriptionV2 } from '../../shared/constants/infomessages'; interface IDetailsListDocumentsExampleState { columns: IColumn[]; items: ISubscriptionsModel[]; + itemsV2: ISubscriptionsV2Model[]; } const Subscriptions: React.FunctionComponent = () => { const history = useHistory(); - const appdiv = document.getElementsByClassName('App')[0] as HTMLElement; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [subscription, setsubscription] = useState([]); const [state, setstate] = useState(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [formError, setFormError] = useState(null); const [loadingSubscription, setLoadingSubscription] = useState(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [loadStatus, setLoadStatus] = useState(true); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [statusList, setStatusList] = useState([]); const [planList, setPlanList] = useState([]); const [dialogVisible, setDialogVisible] = useState(false); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [loadingSubcriptionPost, setloadingSubcriptionPost] = useState(true); const [subscriptionPost, setSubscriptionPost] = useState(getInitialUpdateSubscriptionModel()); const globalContext = useGlobalContext(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [subscriptionV2, setsubscriptionV2] = useState([]); + const [subscriptionV2Selected, setsubscriptionV2Selected] = useState(getInitialSubscriptionV2()); + const [Subscriptionv2DialogVisible, setSubscriptionv2DialogVisible] = useState(false); + const [subscriptionv2PrimaryKey, setSubscriptionv2PrimaryKey] = useState(''); + const [subscriptionv2SecondaryKey, setSubscriptionv2SecondaryKey] = useState(''); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [displayPrimaryKey, setDisplayPrimaryKey] = useState(false); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [displaySecondaryKey, setDisplaySecondaryKey] = useState(false); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() === 'true' ? true : false); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() === 'true' ? true : false); let usr = adalContext.AuthContext.getCachedUser(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars let ownerEmail = ""; if (usr && usr.profile) { if (usr.userName) @@ -58,8 +90,8 @@ const Subscriptions: React.FunctionComponent = () => { } const _onColumnClick = (ev: React.MouseEvent, column: IColumn): void => { - if (column.key != 'operation') { - const { columns, items } = state as IDetailsListDocumentsExampleState; + if (column.key !== 'operation') { + const {columns, items, itemsV2} = state as IDetailsListDocumentsExampleState; const newColumns: IColumn[] = columns.slice(); const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0]; newColumns.forEach((newCol: IColumn) => { @@ -71,8 +103,9 @@ const Subscriptions: React.FunctionComponent = () => { newCol.isSortedDescending = true; } }); - const newItems = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending); - setstate({ items: newItems, columns: newColumns }); + const newItemsv1 = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending) + + setstate({itemsV2: [], items: newItemsv1, columns: newColumns}); } }; @@ -101,11 +134,17 @@ const Subscriptions: React.FunctionComponent = () => { sortDescendingAriaLabel: 'Sorted Z to A', isPadded: true, onRender: (item: ISubscriptionsModel) => { - if (item.status === 'Subscribed' && item.entryPointUrl != "") { - return {item.name}; - } - else - { + /*if (item.status === 'Subscribed' && item.entryPointUrl !== "") { + return {item.name}; + } else { + return {item.name}; + }*/ + if (item.status.toLowerCase() === "subscribed") { + return { + editdetailsV2(item.subscriptionId) + }}>{item.name}; + } else { return {item.name}; } } @@ -240,40 +279,279 @@ const Subscriptions: React.FunctionComponent = () => { horizontalAlign={"space-evenly"} gap={15} horizontal={true} - style={{ float: 'left' }} + style={{float: 'left'}} styles={{ root: {} }} > - {item.status == "Subscribed" && item.provisioningStatus == "Succeeded" ? - { + {item.status === "Subscribed" && item.provisioningStatus === "Succeeded" ? + { updatePlan(item.subscriptionId, item.name, item) - }} /> : null} + }}/> : null} - { + { showHistory(item.subscriptionId) - }} /> + }}/> - {item.status == "Subscribed" && item.provisioningStatus == "Succeeded" ? - { + {item.status === "Subscribed" && item.provisioningStatus === "Succeeded" ? + { deleteSubscription(item.subscriptionId, item.name) - }} /> : null} + }}/> : null} ) } }, ]; - const handleSubmissionErrors = (result: Result): boolean => { - if (!result.success) { - if (result.hasErrors) - toast.error(result.errors.join(', ')); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const columnsV2: IColumn[] = [ + { + key: 'subscriptionName', + name: 'Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'subscriptionName', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + if (item.status.toLowerCase() === "subscribed") { + return { + editdetailsV2(item.subscriptionId) + }}>{item.subscriptionName}; + } else { + return {item.subscriptionName}; + } + } + }, + { + key: 'subscribeid', + name: 'Subscription ID', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'subscriptionId', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.subscriptionId}; + } + }, + { + key: 'productName', + name: 'Product Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'productName', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.productName}; + } + }, + { + key: 'deploymentName', + name: 'Deployment Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'deploymentNamed', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.deploymentName}; + } + }, + { + key: 'status', + name: 'Status', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'status', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.status}; + } + }, + // { + // key: 'baseUrl', + // name: 'Base Url', + // className: '', + // iconClassName: '', + // ariaLabel: '', + // iconName: '', + // isIconOnly: false, + // fieldName: 'baseUrl', + // minWidth: 210, + // maxWidth: 350, + // data: 'string', + // isRowHeader: true, + // isResizable: true, + // isSorted: true, + // isSortedDescending: false, + // sortAscendingAriaLabel: 'Sorted A to Z', + // sortDescendingAriaLabel: 'Sorted Z to A', + // isPadded: true, + // onRender: (item: ISubscriptionsV2Model) => { + // return {item.baseUrl}; + // } + // }, + // { + // key: 'primaryKey', + // name: 'Primary Key', + // className: '', + // iconClassName: '', + // ariaLabel: '', + // iconName: '', + // isIconOnly: false, + // fieldName: 'primaryKey', + // minWidth: 100, + // maxWidth: 100, + // data: 'string', + // isRowHeader: true, + // isResizable: true, + // isSorted: true, + // isSortedDescending: false, + // sortAscendingAriaLabel: 'Sorted A to Z', + // sortDescendingAriaLabel: 'Sorted Z to A', + // isPadded: true, + // onRender: (item: ISubscriptionsV2Model) => { + // return {item.primaryKey}; + // } + // }, + // { + // key: 'secondaryKey', + // name: 'Secondary Key', + // className: '', + // iconClassName: '', + // ariaLabel: '', + // iconName: '', + // isIconOnly: false, + // fieldName: 'secondaryKey', + // minWidth: 100, + // maxWidth: 100, + // data: 'string', + // isRowHeader: true, + // isResizable: true, + // isSorted: true, + // isSortedDescending: false, + // sortAscendingAriaLabel: 'Sorted A to Z', + // sortDescendingAriaLabel: 'Sorted Z to A', + // isPadded: true, + // onRender: (item: ISubscriptionsV2Model) => { + // return {item.secondaryKey}; + // } + // }, + // { + // key: 'operation', + // name: 'Operation', + // className: '', + // iconClassName: '', + // ariaLabel: '', + // iconName: '', + // isIconOnly: false, + // isRowHeader: true, + // isResizable: true, + // isSorted: false, + // fieldName: '', + // minWidth: 100, + // maxWidth: 100, + // onRender: (item: ISubscriptionsV2Model) => { + // return ( + // + // {/* {item.status == "Subscribed" ? + // { + // updatePlan(item.subscriptionId, item.name, item) + // }} /> : null} + + // { + // showHistory(item.subscriptionId) + // }} /> + + // {item.status == "Subscribed" ? + // { + // deleteSubscription(item.subscriptionId, item.name) + // }} /> : null} */} + // + // + // ) + // } + // }, + ]; + + // const handleSubmissionErrors = (result: Result): boolean => { + // if (!result.success) { + // if (result.hasErrors) + // toast.error(result.errors.join(', ')); - return true; - } - return false; - } + // return true; + // } + // return false; + // } const getFormErrorString = (touched, errors, property: string) => { return touched && errors && touched[property] && errors[property] ? errors[property] : ''; @@ -282,19 +560,22 @@ const Subscriptions: React.FunctionComponent = () => { const getStatusList = async (statusarray: string[]) => { let statusDropDown: IDropdownOption[] = []; statusDropDown.push( - { key: 'all', text: 'All' }, + {key: 'all', text: 'All'}, ) statusarray.map((value, index) => { - statusDropDown.push( - { key: value.toLowerCase(), text: value }, - ) + return ( + statusDropDown.push( + {key: value.toLowerCase(), text: value}, + )) }) setStatusList(statusDropDown); } const getSubscriptions = async () => { setLoadingSubscription(true); - const results = await SubscriptionsService.list(ownerEmail as string); + let results: any; + results = await SubscriptionsService.list(ownerEmail as string); + if (results && !results.hasErrors && results.value) { setLoadStatus(true); @@ -308,10 +589,16 @@ const Subscriptions: React.FunctionComponent = () => { } getStatusList(stringArray); setsubscription(results.value); - setstate({ items: results.value, columns: columns }); + setstate({ + items: results.value, + columns: columns, + itemsV2: [] + }); } else { setsubscription([]); - setstate({ items: [], columns: columns }); + setsubscriptionV2([]); + setstate({itemsV2: [], items: [], columns: columns}); + if (results.hasErrors) { // TODO: display errors alert(results.errors.join(', ')); @@ -331,24 +618,21 @@ const Subscriptions: React.FunctionComponent = () => { if (PlanResponse.value && PlanResponse.success) { var Plans = PlanResponse.value; Plans.map((item, index) => { - planList.push( - { - key: item.planName, - text: item.planName - }) + return ( + planList.push( + { + key: item.planName, + text: item.planName + }) + ) }) } setPlanList([...planList]); } useEffect(() => { - //appdiv.classList.add('mainsubscriptionlist'); getSubscriptions(); - - return () => { - console.log('will unmount'); - //appdiv.classList.remove('mainsubscriptionlist'); - } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const updatePlan = async (subscriptionId: string, subscriptionName: string, selectedSubscription: ISubscriptionsModel) => { @@ -388,7 +672,7 @@ const Subscriptions: React.FunctionComponent = () => { } const _onItemInvoked = (item: any) => { - alert(`Item invoked: ${item.name}`); + //alert(`Item invoked: ${item.name}`); } @@ -415,6 +699,84 @@ const Subscriptions: React.FunctionComponent = () => { btnsubmit.click(); } + const hideSubscriptionv2Dialog = (): void => { + setSubscriptionv2DialogVisible(false); + }; + + const convertToAsterisk = (value: string): string => { + let returnvalue = ''; + for (let index = 0; index < value.length; index++) { + returnvalue = returnvalue + '*'; + } + return returnvalue; + } + + const editdetailsV2 = async (subscriptionId: string) => { + + const dataResponse = await SubscriptionsService.getV2(subscriptionId); + // // Global errors should have already been handled for get requests by this point + if (dataResponse.value && dataResponse.success) { + var data = dataResponse.value as ISubscriptionsV2Model; + setSubscriptionv2PrimaryKey(convertToAsterisk(data.primaryKey)); + setSubscriptionv2SecondaryKey(convertToAsterisk(data.secondaryKey)); + setsubscriptionV2Selected(data); + setSubscriptionv2DialogVisible(true); + } else { + let errorMessages: IError[] = []; + + errorMessages.concat(dataResponse.errors); + + if (errorMessages.length > 0) { + toast.error(errorMessages.join(', ')); + } + } + }; + + const showKey = (key: string, subscriptionV2Selected: ISubscriptionsV2Model) => { + let convertedstring = ''; + if (key === "primaryKey") { + convertedstring = convertToAsterisk(subscriptionV2Selected.primaryKey); + if (subscriptionv2PrimaryKey === convertedstring) { + setDisplayPrimaryKey(true); + setSubscriptionv2PrimaryKey(subscriptionV2Selected.primaryKey); + } else { + setDisplayPrimaryKey(false); + setSubscriptionv2PrimaryKey(convertedstring); + } + } else { + convertedstring = convertToAsterisk(subscriptionV2Selected.secondaryKey); + if (subscriptionv2SecondaryKey === convertedstring) { + setDisplaySecondaryKey(true); + setSubscriptionv2SecondaryKey(subscriptionV2Selected.secondaryKey); + } else { + setDisplaySecondaryKey(false); + setSubscriptionv2SecondaryKey(convertedstring); + } + } + } + + const RegenerateKey = async (subscriptionId: string, key: string) => { + + globalContext.showProcessing(); + + let subscriptionsV2RefreshKeyModel: ISubscriptionsV2RefreshKeyModel = {keyName: '', subscriptionId: ''}; + subscriptionsV2RefreshKeyModel.subscriptionId = subscriptionId; + subscriptionsV2RefreshKeyModel.keyName = key; + let convertedstring = ''; + let results = await SubscriptionsService.RefreshKey(subscriptionsV2RefreshKeyModel); + if (results && !results.hasErrors && results.value) { + setsubscriptionV2Selected(results.value); + if (key === "primaryKey") { + convertedstring = convertToAsterisk(results.value.primaryKey); + displayPrimaryKey ? setSubscriptionv2PrimaryKey(results.value.primaryKey) : setSubscriptionv2PrimaryKey(convertedstring); + } else { + convertedstring = convertToAsterisk(results.value.secondaryKey); + displayPrimaryKey ? setSubscriptionv2SecondaryKey(results.value.secondaryKey) : setSubscriptionv2SecondaryKey(convertedstring); + } + } + globalContext.hideProcessing(); + } + return ( { } }} > - + :
- All Offer Subscriptions + All Offer Subscriptions
- { - _onColumnClick(event as React.MouseEvent, column as IColumn) - }} - /> + { + { + _onColumnClick(event as React.MouseEvent, column as IColumn) + }} + /> + } + +
} -
diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts index 971c596..f6f903c 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts @@ -1,9 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; import {ObjectSchema} from "yup"; import { ICreateSubscriptionModel, - ILandingModel, - IOperationHistoryModel, IParamModel, ISubscriptionFormModel, IUpdateSubscriptionModel + IOperationHistoryModel, ISubscriptionsV2Model, IUpdateSubscriptionModel } from "../../../models"; export const getInitialCreateSubscriptionModel = (): ICreateSubscriptionModel => { @@ -32,6 +34,21 @@ export const getInitialUpdateSubscriptionModel = (): IUpdateSubscriptionModel => } }; +export const getInitialSubscriptionV2 = (): ISubscriptionsV2Model => { + return { + subscriptionId: '', + subscriptionName: '', + userId: '', + productName: '', + deploymentName: '', + status: '', + baseUrl: '', + primaryKey: '', + secondaryKey: '' + } +}; + + export interface OperationHistoryModel { data: IOperationHistoryModel[] } @@ -76,7 +93,7 @@ export const subscriptionValidator: ObjectSchema = yup const planName: string = this.parent.CurrentPlanName; console.log('val: ' + value); - if (!value || value == "") + if (!value || value === "") return true; return value.toLowerCase() !== planName.toLowerCase(); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/serviceWorker.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/serviceWorker.ts index 15d90cb..2c1337f 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/serviceWorker.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/serviceWorker.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + // This optional code is used to register a service worker. // register() is not called by default. diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplateParameterService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplateParameterService.ts index d5c1844..3d2f874 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplateParameterService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplateParameterService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {IARMTemplateParameterModel, Result} from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplatesService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplatesService.ts index d8f85b0..4c8964d 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplatesService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ArmTemplatesService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {IARMTemplateModel, Result} from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/EndUserLandingService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/EndUserLandingService.ts index f025315..b43b770 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/EndUserLandingService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/EndUserLandingService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {ServiceBase} from "../services/ServiceBase"; import {IResolveTokenModel, Result} from "../models"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/IpConfigService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/IpConfigService.ts index e0937f6..979db3a 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/IpConfigService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/IpConfigService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { IIpConfigModel, Result } from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferParameterService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferParameterService.ts index cee8b81..96bf4f5 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferParameterService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferParameterService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {Result} from "../models"; import {IOfferParameterModel} from "../models/IOfferParameterModel"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferService.ts index 286a210..424d152 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/OfferService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {IOfferModel, Result} from "../models"; import {IOfferWarningsModel} from "../models/IOfferWarningsModel"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/PlansService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/PlansService.ts index d876290..b960477 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/PlansService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/PlansService.ts @@ -1,7 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { IPlanModel, IPlanWarningsModel, Result, IARMTemplateModel,IRestrictedUsersModel } from "../models"; import { v4 as uuid } from "uuid"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars let armTemplateModellist: IARMTemplateModel[]; export default class PlansService extends ServiceBase { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ServiceBase.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ServiceBase.ts index 94549b1..863174d 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ServiceBase.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/ServiceBase.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {Result} from "../models"; import * as jsonToUrl from "json-to-url"; import Axios, {AxiosRequestConfig, AxiosResponse} from "axios"; @@ -87,10 +90,10 @@ export abstract class ServiceBase { console.log(error); // parse the server's error if one was provided - if (error.response && error.response.data != "") { + if (error.response && error.response.data !== "") { // validation error - if (error.response.status == 400) { + if (error.response.status === 400) { if (!Array.isArray(error.response.data)) result = new Result(null, false,[{method_error: ["One or more validation errors have occurred but we were unable to parse them. Please inspect the console for more information."]}]); else @@ -157,10 +160,10 @@ export abstract class ServiceBase { console.log(error); // parse the server's error if one was provided - if (error.response.data != "") { + if (error.response.data !== "") { // validation error - if (error.response.status == 400) { + if (error.response.status === 400) { if (!Array.isArray(error.response.data)) result = new Result(null, false,[{method_error: ["One or more validation errors have occurred but we were unable to parse them. Please inspect the console for more information."]}]); else diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/SubscriptionsService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/SubscriptionsService.ts index 79547eb..cb2a75b 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/SubscriptionsService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/SubscriptionsService.ts @@ -1,11 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {ServiceBase} from "../services/ServiceBase"; import { ICreateSubscriptionModel, - ILandingModel, + IOperationHistoryModel, ISubscriptionsModel, ISubscriptionWarningsModel, IUpdateSubscriptionModel, - Result + Result, + ISubscriptionsV2RefreshKeyModel, + ISubscriptionsV2Model } from "../models"; export default class SubscriptionsService extends ServiceBase { @@ -93,4 +98,64 @@ export default class SubscriptionsService extends ServiceBase { // return result; return result; } + + //#region SubscriptionV2 + + public static async listV2(): Promise> { + + var result = await this.requestJson({ + url: `/apisubscriptions`, + method: "GET" + }); + + return result; + } + + public static async getV2(subscriptionId): Promise> { + + var result = await this.requestJson({ + url: `/apisubscriptions/${subscriptionId}`, + method: "GET" + }); + return result; + } + + public static async createV2(model: ISubscriptionsV2Model): Promise> { + var result = await this.requestJson({ + url: `/apisubscriptions/create`, + method: "POST", + data: model + }); + + return result; + } + public static async updateV2(model: ISubscriptionsV2Model): Promise> { + var result = await this.requestJson({ + url: `/apisubscriptions/${model.subscriptionId}`, + method: "PUT", + data: model + }); + + return result; + } + + public static async RefreshKey(model: ISubscriptionsV2RefreshKeyModel): Promise> { + var result = await this.requestJson({ + url: `/apisubscriptions/${model.subscriptionId}/regenerateKey`, + method: "POST", + data: model + }); + + return result; + } + + public static async deleteV2(subscriptionId: string): Promise> { + var result = await this.requestJson>({ + url: `/subscriptions/${subscriptionId}`, + method: "DELETE" + }); + return result; + } + + //#endregion } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksParametersService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksParametersService.ts index 0ef79f9..79f6e71 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksParametersService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksParametersService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { IWebHookParameterModel, Result } from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksService.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksService.ts index 3ae4dc2..9f74793 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/services/WebHooksService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { IWebHookModel, Result } from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/AuthRoute.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/AuthRoute.tsx index c77dc0a..a1f0bb6 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/AuthRoute.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/AuthRoute.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; -import {Redirect, Route, RouteProps} from 'react-router-dom'; +import {Route, RouteProps} from 'react-router-dom'; //import {WebRoute} from '../constants/routes'; export const AuthRoute: React.FC = (props) => { - + + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { location } = props; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/FormLabel.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/FormLabel.tsx index d3b4f7a..a97599c 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/FormLabel.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/FormLabel.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; -import {Icon, Stack, TooltipHost} from "office-ui-fabric-react"; -import {v4 as uuid} from "uuid"; +import {Stack} from "office-ui-fabric-react"; import InfoToolTip from "./InfoToolTip"; type FormLabelProps = { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/NotFound.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/NotFound.tsx index 1f3e7c0..de48f32 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/NotFound.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/components/NotFound.tsx @@ -10,6 +10,7 @@ interface ParentProps { type Props = ParentProps; export const NotFound: React.FC = (props) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { title, message, statusCode } = props; const notFoundMessage = message || "Not Found"; return ( diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/infomessages.tsx b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/infomessages.tsx index 5d52d41..5d2650e 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/infomessages.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/infomessages.tsx @@ -26,4 +26,16 @@ export const Offers = restrictedAudience:"Restricted Audience (Tenant IDs) - Assign the audience that will have access to this private plan."+ "

Note: This must correspond to the Restricted Audience specified on the Plan overview tab (Plan audience, check “This is a private plan.”) of the partner center." } +} + +export const SubscriptionV2 = +{ + Subscription: { + ID:'', + subscriptionName:'', + EndPoint:'', + Keys:'', + SecondaryKey: '', + PrimaryKey:'' + } } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/routes.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/routes.ts index daa2af8..0dcc6e1 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/routes.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/constants/routes.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export enum WebRoute { Home = '/', Subscriptions = '/Subscriptions', diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/formUtils/utils.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/formUtils/utils.ts index 13d5600..32d7ec4 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/formUtils/utils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/shared/formUtils/utils.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { useState } from 'react'; import {Result} from "../../models"; @@ -57,7 +60,7 @@ export const handleSubmissionErrorsGeneral = (setErrors: any, setSubmitting: any let setupValidationElement = false; for(let err of result.errors) { - if (Object.keys(err).length == 0) { + if (Object.keys(err).length === 0) { alert('Encountered an error parsing the returned error values'); return true; } @@ -87,7 +90,7 @@ export const handleSubmissionErrorsGeneral = (setErrors: any, setSubmitting: any errorObj[formKey][key] = Object.values(err)[0].join(', '); } - if (key == "method_error") + if (key === "method_error") setFormError(Object.values(err)[0].join('
')); else setErrors(errorObj); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/types/yup/index.d.ts b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/types/yup/index.d.ts index 1bc5187..b351775 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/types/yup/index.d.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/enduser_client/src/types/yup/index.d.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {ArraySchema, ObjectSchema, default as yup} from 'yup' declare module 'yup' { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/package.json b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/package.json index a7c159d..a794152 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/package.json +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/package.json @@ -16,7 +16,10 @@ "office-ui-fabric-react": "^6.211.0", "react": "^16.13.1", "react-adal": "^0.5.0", + "react-confirm-alert": "^2.6.1", + "react-copy-to-clipboard": "^5.0.2", "react-dom": "^16.13.1", + "react-html-parser": "^2.0.2", "react-moment": "^0.9.7", "react-router-dom": "^5.1.2", "react-scripts": "^3.4.1", @@ -24,8 +27,7 @@ "react-toastify": "^5.5.0", "typescript": "3.6.4", "uuid": "^3.3.3", - "yup": "^0.27.0", - "react-confirm-alert": "^2.6.1" + "yup": "^0.27.0" }, "devDependencies": { "@types/react-adal": "^0.4.2", @@ -35,10 +37,10 @@ }, "scripts": { "start": "PORT=4321 HTTPS=true react-scripts start", - "winstart": "set HTTPS=true&&set port=44321&&react-scripts start", + "winstart": "set HTTPS=true&&set port=4321&&react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/public/config.js b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/public/config.js index 2abedfc..e4cd8fb 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/public/config.js +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/public/config.js @@ -1,9 +1,18 @@ +// var Configs = { +// API_ENDPOINT: "https://lunaaitest-apiapp.azurewebsites.net/api/", //"https://lunatest-apiapp.azurewebsites.net/api/", // https://luna-dev-api.azurewebsites.net/api/ +// ISV_NAME: "Microsoft", +// AAD_APPID: "b9285d6f-f251-40c0-8123-7aa49ef61d0e", //"9ee174e4-e4ac-4a85-b6e3-85d52ca5f842", +// AAD_ENDPOINT: "https://lunaaitest-isvapp.azurewebsites.net", +// HEADER_BACKGROUND_COLOR: "#004578", +// ENABLE_V1: "false", +// ENABLE_V2: "true" +// } var Configs = { - API_ENDPOINT: "https://lunatest-apiapp.azurewebsites.net/api/", + API_ENDPOINT: "https://lunamgmtprod.azurewebsites.net/api/", ISV_NAME: "Microsoft", - AAD_APPID: "50a6b69d-325a-4c02-aff3-51703d904789", - AAD_ENDPOINT: "https://lunatest-isvapp.azurewebsites.net", - HEADER_BACKGROUND_COLOR: "#118811", + AAD_APPID: "11333002-6e05-4478-b423-c959d2600bc2", + AAD_ENDPOINT: "https://lunamgmtisv.azurewebsites.net", + HEADER_BACKGROUND_COLOR: "#004578", ENABLE_V1: "true", ENABLE_V2: "true" } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.css b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.css index 4cc237d..8ad60bb 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.css +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.css @@ -200,15 +200,15 @@ div.ms-TextField > span div span { height: 60px; } -.fixed_header tbody{ - display:block; - overflow:auto; - max-height:145px; - width:100%; +.fixed_header tbody { + display: block; + overflow: auto; + max-height: 145px; + width: 100%; } -.fixed_header thead tr{ - display:block; +.fixed_header thead tr { + display: block; } /*Plan ends*/ @@ -754,8 +754,7 @@ table.offer thead tr th { .reviewoffer table.offer thead tr th:last-child { border-right: none; } -.subbutton -{ +.subbutton { /* position: absolute; top: 10%; right: 5%; */ @@ -764,7 +763,7 @@ table.offer thead tr th { right: 0; margin-right: 5%; } -.subscription-details-separator{ +.subscription-details-separator { position: relative; padding-left: 20px; box-sizing: border-box; @@ -772,19 +771,18 @@ table.offer thead tr th { width: 70%; } .subscription-details-separator:after { - content: ''; + content: ""; border: solid 1px rgba(102, 102, 102, 0.5); left: 0; top: 0; bottom: 0; position: absolute; margin-top: 10px; - margin-bottom: 10px; + margin-bottom: 10px; } /*Loading css*/ -.loadingspinner .ms-Spinner-circle.ms-Spinner--large -{ +.loadingspinner .ms-Spinner-circle.ms-Spinner--large { width: 100px; height: 100px; } @@ -888,53 +886,92 @@ table.offer thead tr th { } /*Loading css*/ -.ms-Layer -{ +.ms-Layer { z-index: 999 !important; } -.react-confirm-alert-overlay -{ - z-index: 9999 !important;; +.react-confirm-alert-overlay { + z-index: 9999 !important; } -.react-confirm-alert-button-group > button:first-child -{ +.react-confirm-alert-button-group > button:first-child { background: rgb(0, 120, 212) !important; - color:rgb(255, 255, 255) !important; + color: rgb(255, 255, 255) !important; } -.react-confirm-alert-button-group > button:last-child -{ +.react-confirm-alert-button-group > button:last-child { background: rgb(234, 234, 234) !important; - color:rgb(51, 51, 51) !important; + color: rgb(51, 51, 51) !important; } -.deploymentmodalpopup -{ -width: 100%; +.deploymentmodalpopup { + width: 100%; } -.deploymentmodalpopup .ms-Dialog-main -{ -max-width: 100%; -min-width: 45%; +.deploymentmodalpopup .ms-Dialog-main { + max-width: 100%; + min-width: 45%; } -.Productnav-header -{ - height: 10px!important; +.Productnav-header { + height: 10px !important; border-bottom: none; } -.OrangeButton -{ - background-color: #ED7D31 !important; +.OrangeButton { + background-color: #ed7d31 !important; } .wdth_100_per { width: 100%; } -.padd-right-10_Per -{ +.padd-right-10_Per { padding-right: 10%; } -.mar-right-2_Per -{ +.mar-right-2_Per { margin-right: 2% !important; -} \ No newline at end of file +} + +.subv2ipinput { + width: 100%; + float: left; +} +.subv2ipinputw_90 { + width: 88%; +} +.subv2ipinputcopy { + display: flex; + width: 100%; + padding-left: 5px; + padding-right: 5px; +} +.copied { + color: #1885d8; + background-color: #d0d0d0; + border-radius: 5px; +} +.source .ms-Dropdown-container { + width: 150px !important; +} +.versionProjectupload { + +} +.versionProjectuploadbrowsebutton { + background-color: #0078d7; + color: #fff; + padding: 14px; + vertical-align: middle; + padding-bottom: 11px; + line-height: 45px; +} +.filetittle { + width: 200px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + display: list-item; + float: left; + border: 1px solid #0e0e0e; + line-height: 40px; +} +.versionuploadbutton { + padding: 10px; + line-height: 22px; + display: flex; + width: 40px; +} diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.tsx index 578d695..60c1b80 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/App.tsx @@ -43,7 +43,6 @@ const App: React.FC = () => { - diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/adalConfig.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/adalConfig.ts index 32c4d9a..4138db3 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/adalConfig.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/adalConfig.ts @@ -1,5 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {AdalConfig, adalGetToken, AuthenticationContext} from 'react-adal'; -import { selectProperties } from 'office-ui-fabric-react'; +//import { selectProperties } from 'office-ui-fabric-react'; // Endpoint URL export const endpoint = window.Configs.AAD_ENDPOINT as string; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Content.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Content.tsx index 8518e32..eda1252 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Content.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Content.tsx @@ -10,8 +10,8 @@ const Content: React.FunctionComponent = (props) => { const history = useHistory(); const location = useLocation(); - const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() == 'true' ? true : false); - const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() == 'true' ? true : false); + const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() === 'true' ? true : false); + const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() === 'true' ? true : false); let offersTabActive = (location.pathname.toLowerCase().startsWith('/offers') || location.pathname.toLowerCase().startsWith('/modifyoffer')); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Layout.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Layout.tsx index a933076..8f85d85 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Layout.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/Layout.tsx @@ -26,8 +26,8 @@ const Layout: React.FunctionComponent = (props) => { const location = useLocation(); let genericContentWrapper = true; - const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() == 'true' ? true : false); - const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() == 'true' ? true : false); + const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() === 'true' ? true : false); + const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() === 'true' ? true : false); let modifyOfferActive = (location.pathname.toLowerCase().startsWith('/modifyoffer')); let noVersionActive = (location.pathname.toLowerCase().startsWith('/noversion')); @@ -42,13 +42,13 @@ const Layout: React.FunctionComponent = (props) => { genericContentWrapper = false; let offerName: string | null = null; - let productId: string | null = null; + let productName: string | null = null; if (modifyOfferActive || reviewOfferActive || subscriptionDetailActive) { // get offerName from the path var idx = location.pathname.indexOf('/', 1); - + var idx2 = 0; if (idx > 0) { - var idx2 = location.pathname.indexOf('/', idx + 1); + idx2 = location.pathname.indexOf('/', idx + 1); if (idx2 > 0) { offerName = location.pathname.toLowerCase().substr(idx + 1, idx2 - (idx + 1)); @@ -59,16 +59,16 @@ const Layout: React.FunctionComponent = (props) => { } if (modifyProductActive) { - var idx = location.pathname.indexOf('/', 1); + idx = location.pathname.indexOf('/', 1); if (idx > 0) { - var idx2 = location.pathname.indexOf('/', idx + 1); + idx2 = location.pathname.indexOf('/', idx + 1); if (idx2 > 0) { - productId = location.pathname.toLowerCase().substr(idx + 1, idx2 - (idx + 1)); + productName = location.pathname.toLowerCase().substr(idx + 1, idx2 - (idx + 1)); } else - productId = location.pathname.toLowerCase().substr(idx + 1); + productName = location.pathname.toLowerCase().substr(idx + 1); } } @@ -90,7 +90,7 @@ const Layout: React.FunctionComponent = (props) => { )} {modifyProductActive && v2Enabled && ( - + {children} )} diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/ProductContent.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/ProductContent.tsx index 5b8509a..a142437 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/ProductContent.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/layout/ProductContent.tsx @@ -1,154 +1,56 @@ import React, { useEffect, useState } from 'react'; -import { getTheme, PrimaryButton, Stack } from 'office-ui-fabric-react'; +import { DefaultButton, FontIcon, getTheme, PrimaryButton, Stack, TextField } from 'office-ui-fabric-react'; import { useHistory, useLocation } from 'react-router'; -import { LayoutHelper, LayoutHelperMenuItem } from "./Layout"; +//import { LaoutHelper, LayoutHelperMenuItem } from "./Layout"; import ProductService from "../services/ProductService"; -import {initialProductList} from '../routes/Products/formUtils/ProductFormUtils' +import { initialProductValues, deleteProductValidator } from '../routes/Products/formUtils/ProductFormUtils' import { IProductModel } from "../models"; import { useGlobalContext } from "../shared/components/GlobalProvider"; -import { confirmAlert } from 'react-confirm-alert'; // Import -import 'react-confirm-alert/src/react-confirm-alert.css'; // Import css +import 'react-confirm-alert/src/react-confirm-alert.css'; +import { handleSubmissionErrorsForForm } from "../shared/formUtils/utils"; +import { toast } from "react-toastify"; +import { DialogBox } from '../shared/components/Dialog'; +import { Formik } from 'formik'; type ProductProps = { - productId: string | null; + productName: string | null; }; const ProductContent: React.FunctionComponent = (props) => { - const { productId } = props; + const { productName } = props; const history = useHistory(); const location = useLocation(); const globalContext = useGlobalContext(); - const [hideSave, setHideSave] = useState(false); - - // const layoutHelper: LayoutHelper = { - // menuItems: - // [ - // { - // title: "Info", - // paths: [`/modifyproduct/${productId}/info`], - // menuClick: () => { - // preventDataLoss('Info'); - // } - // }, - // { - // title: "Parameters", - // paths: [`/modifyproduct/${productId}/parameters`], - // menuClick: () => { - // preventDataLoss('Parameters'); - // } - // }, - // { - // title: "IP Addresses", - // paths: [`/modifyproduct/${productId}/IpConfigs`], - // menuClick: () => { - // preventDataLoss('IpConfigs'); - // } - // }, - // { - // title: "ARM Templates", - // paths: [`/modifyproduct/${productId}/ArmTemplates`], - // menuClick: () => { - // preventDataLoss('ArmTemplates'); - // } - // }, - // { - // title: "Webhooks", - // paths: [`/modifyproduct/${productId}/WebHooks`], - // menuClick: () => { - // preventDataLoss('WebHooks'); - // } - // }, - // { - // title: "Meters", - // paths: [`/modifyproduct/${productId}/Meters`], - // menuClick: () => { - // preventDataLoss('Meters'); - // } - // }, - // { - // title: "Plans", - // paths: [`/modifyproduct/${productId}/Plans`], - // menuClick: () => { - // preventDataLoss('Plans'); - // } - // } - // ] - // }; - - // const preventDataLoss = (pathName: string) => { - - // if (globalContext.isDirty || globalContext.isSecondaryDirty) { - - // confirmAlert({ - // title: 'Data Loss Prevention', - // message: 'You have unsaved data that will be lost, do you wish to continue?', - // buttons: [ - // { - // label: 'No', - // onClick: () => { } - // }, - // { - // label: 'Yes', - // onClick: () => { - // globalContext.setFormDirty(false); - // history.push(`/modifyproduct/${productId}/${pathName}`); - // } - // } - // ] - // }); - // } - // else { - // history.push(`/modifyproduct/${productId}/${pathName}`); - // } - - // } - - // const isNavItemActive = (paths: string[]): boolean => { - // let found = false; - // for (let i = 0; i < paths.length; i++) { - // found = location.pathname.toLowerCase() === paths[i].toLowerCase(); - // if (found) { - // break; - // } - // } - - // return found; - // }; + const [productModel, setProductModel] = useState(initialProductValues); + const [formError, setFormError] = useState(null); + + const [ProductDeleteDialog, setProductDeleteDialog] = useState(false); + const [selectedProductName, setSelectedProductName] = useState(''); const theme = getTheme(); - const getProductInfo = async (productId: string) => { + const getProductInfo = async (productName: string) => { - // let response = await ProductService.get(offerName); + let response = await ProductService.get(productName); - let response = initialProductList.filter(p=>p.productId==productId)[0]; - setProductModel({ ...response}) - // if (!response.hasErrors && response.value) { + //let response = initialProductList.filter(p=>p.productName==productName)[0]; - // setProductModel({ ...response.value }); - // } + if (!response.hasErrors && response.value) { + setProductModel({ ...response.value }) + } } - const [productModel, setProductModel] = useState({ - hostType: '', - owner: '', - productId: '', - productType: '', isDeleted: false, - isSaved: false, - isModified: false, - isNew: true, - clientId: "" - }); + useEffect(() => { - if (productId) - getProductInfo(productId); + if (productName) + getProductInfo(productName); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [productId]); + }, [productName]); useEffect(() => { @@ -157,11 +59,50 @@ const ProductContent: React.FunctionComponent = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [history.location, location.pathname]); - const handleFormSubmission = async (e) => { - if (globalContext.saveForm) - await globalContext.saveForm(); + const handleProductDeletion = async (e) => { + + setSelectedProductName(productName as string); + setProductDeleteDialog(true); + + // globalContext.showProcessing(); + + // // determine if there are any deployments or aml workspaces, if there are, prevent the deletion + // var deploymentsResponse = await ProductService.getDeploymentListByProductName(productName as string); + + // if (deploymentsResponse.success) { + // if (deploymentsResponse.value && deploymentsResponse.value.length > 0) { + // toast.error("You must delete all deployments for the product first."); + // globalContext.hideProcessing(); + // return; + // } + // } + + // const deleteResult = await ProductService.delete(productName as string); + + // if (handleSubmissionErrorsForForm((item) => {},(item) => {}, setFormError, 'product', deleteResult)) { + // toast.error(formError); + // globalContext.hideProcessing(); + // return; + // } + + // globalContext.hideProcessing(); + // toast.success("Product Deleted Successfully!"); + // history.push(`/products/`); }; + const OnCancel = async (e) => { + history.push(`/products/`); + }; + + const CloseProductDeleteDialog = () => { + setProductDeleteDialog(false); + } + + const getDeleteProductErrorString = (touched, errors, property: string) => { + return (touched.selectedProductId && errors.selectedProductId && touched[property] && errors[property]) ? errors[property] : ''; + }; + + return ( = (props) => { flexGrow: 0 } }}> - + Product Details @@ -213,7 +154,7 @@ const ProductContent: React.FunctionComponent = (props) => { ID: - {productModel.productId} + {productModel.productName} Product Type: @@ -238,14 +179,13 @@ const ProductContent: React.FunctionComponent = (props) => { verticalAlign={"center"} verticalFill={true} horizontalAlign={"end"} - gap={8} + gap={15} > - {/* {!hideSave && - - } */} - - - + + Delete + + + @@ -297,6 +237,90 @@ const ProductContent: React.FunctionComponent = (props) => { + + { + CloseProductDeleteDialog(); + }} + submitonClick={() => { + const btnsubmit = document.getElementById('btnProductDelete') as HTMLButtonElement; + btnsubmit.click(); + }} + children={ + + { + + globalContext.showProcessing(); + + // determine if there are any deployments or aml workspaces, if there are, prevent the deletion + var deploymentsResponse = await ProductService.getDeploymentListByProductName(productName as string); + + if (deploymentsResponse.success) { + if (deploymentsResponse.value && deploymentsResponse.value.length > 0) { + toast.error("You must delete all deployments for the product first."); + globalContext.hideProcessing(); + return; + } + } + + const deleteResult = await ProductService.delete(productName as string); + + if (handleSubmissionErrorsForForm((item) => {},(item) => {}, setFormError, 'product', deleteResult)) { + toast.error(formError); + globalContext.hideProcessing(); + return; + } + + globalContext.hideProcessing(); + toast.success("Product Deleted Successfully!"); + history.push(`/products/`); + }} + > + {({ handleChange, values, handleBlur, touched, errors, handleSubmit }) => ( +
+ + + + + + + + + + +
+ Are you sure you want to delete Product? +
+ { + + Type the product Id +
+ +
+ } +
+
+ +
+
+ )} +
+
+ } /> ); }; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IBaseModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IBaseModel.ts index 21b4a4c..a8a6dcb 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IBaseModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IBaseModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export interface IBaseModel { isNew?: boolean; isDeleted?: boolean; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IIpConfigModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IIpConfigModel.ts index 22643e2..ea1d44b 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IIpConfigModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IIpConfigModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {IBaseModel} from "./IBaseModel"; export interface IIpConfigModel extends IBaseModel { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferModel.ts index 426c407..6141546 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {IBaseModel} from "./IBaseModel"; export interface IOfferModel extends IBaseModel { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferParameterModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferParameterModel.ts index afce6c5..eec8a8c 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferParameterModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferParameterModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {IBaseModel} from "./IBaseModel"; export interface IOfferParameterModel extends IBaseModel { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferReviewModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferReviewModel.ts index 7a21830..9bdeee0 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferReviewModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferReviewModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {IOfferModel} from "./IOfferModel"; import {IOfferParameterModel} from "./IOfferParameterModel"; import {IIpConfigModel} from "./IIpConfigModel"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferWarningsModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferWarningsModel.ts index 320bf2d..2136d3c 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferWarningsModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IOfferWarningsModel.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export interface IOfferWarningsModel { subscriptionId: string, warningMessage: string, diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IProductModel.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IProductModel.ts index fd9bf90..0844fcd 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IProductModel.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/IProductModel.ts @@ -1,46 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { IBaseModel } from "./IBaseModel"; export interface IProductModel extends IBaseModel { - productId: string; + productName: string; productType: string; hostType: string; owner: string; + createdTime?: string; + lastUpdatedTime?: string; Idlist?: string; selectedProductId?: string - selectedProductindex?: number + selectedProductindex?: number } -// export interface IDeploymentsModel extends IBaseModel { -// id: string; -// versionId: string; -// realTimePredictApi: string; -// apiAuthenticationKey: string; -// } +export interface ILookupType { + id:string; + displayName:string; +} export interface IDeploymentsModel extends IBaseModel { - productId:string; - deploymentId: string; + productName:string; + deploymentName: string; description:string; - versionId: string; - // realTimePredictApi: string; - // apiAuthenticationKey: string; - //deploymentVersionList:IDeploymentVersionModel[]; + versionName: string; + selecteddeploymentName: string; } export interface IDeploymentVersionModel { - productID:string; - deploymentId:string; - versionId: string; - trainModelApi:string; - BatchInferenceAPI:string; - DeployModelAPI:string; - AuthenticationType:string; - AMLWorkspaceId:string; + productName:string; + deploymentName:string; + versionName: string; + realTimePredictAPI: string; + trainModelId:string; + batchInferenceId:string; + deployModelId:string; + authenticationType:string; + authenticationKey:string; + amlWorkspaceName:string; + advancedSettings:string | null; + selectedVersionName:string; + versionSourceType:string; + gitUrl:string; + gitPersonalAccessToken:string; + gitVersion:string; + projectFileUrl:string; + projectFileContent:string; } export interface IAMLWorkSpaceModel extends IBaseModel{ - workspaceId:string; + workspaceName:string; resourceId:string; - aADApplicationId:string; - aADApplicationSecret:string; + aadTenantId:string; + registeredTime:string; + aadApplicationId:string; + aadApplicationSecrets:string; + selectedWorkspaceName:string; +} + +export interface ISourceModel { + displayName:string; + id:string; +} + +export interface IPipeLineModel { + displayName:string; + id:string; + lastUpdatedTime:string; + description:string; } diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/ISubscriptionsModel.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/ISubscriptionsModel.tsx index eeb0d92..9365e00 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/ISubscriptionsModel.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/ISubscriptionsModel.tsx @@ -96,13 +96,18 @@ export interface ISubscriptionsWarnings { export interface ISubscriptionsV2Model { subscriptionId: string, - name: string, + subscriptionName: string, userId: string, - productId: string, - deploymentId: string, + productName: string, + deploymentName: string, status: string, baseUrl: string, primaryKey: string, secondaryKey: string +} + +export interface ISubscriptionsV2RefreshKeyModel { + subscriptionId: string, + keyName:string; } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/Result.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/Result.ts index d547715..b870cb3 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/Result.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/Result.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export interface IError { //[key: string] : ISubError diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/index.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/index.ts index 9bd2300..d14d8c5 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/index.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/models/index.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export * from './IARMTemplateParameterModel'; export * from './IARMTemplateModel'; export * from './IIpConfigModel'; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/react-app-env.d.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/react-app-env.d.ts index 6431bc5..dbc18b2 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/react-app-env.d.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/react-app-env.d.ts @@ -1 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /// diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Home/Home.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Home/Home.tsx index ca0fab9..4e7e05a 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Home/Home.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Home/Home.tsx @@ -1,11 +1,10 @@ import React from 'react'; import {Redirect} from "react-router"; import {WebRoute} from "../../shared/constants/routes"; -import OfferContent from "../../layout/OfferContent"; const Home: React.FunctionComponent = () => { - const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() == 'true' ? true : false); - const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() == 'true' ? true : false); + const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() === 'true' ? true : false); + const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() === 'true' ? true : false); if (v1Enabled) { return ( diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/ModifyPlan.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/ModifyPlan.tsx index d5c0da7..8bbd5e8 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/ModifyPlan.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/ModifyPlan.tsx @@ -1,9 +1,8 @@ import React, { useEffect, useState } from 'react'; import { - ChoiceGroup, DefaultButton, DialogFooter, + DefaultButton, DialogFooter, Dropdown, - FontIcon, - IChoiceGroupOption, + FontIcon, IDropdownOption, MessageBar, MessageBarType, @@ -16,7 +15,6 @@ import { FieldArray, useFormikContext } from "formik"; import { getInitialCustomMeterDimension, getInitialPlan, - getInitialRestrictedUser, IPlanFormValues } from "./formUtils/planFormUtils"; import PlansService from "../../services/PlansService"; @@ -26,16 +24,14 @@ import { Loading } from '../../shared/components/Loading'; import { IARMTemplateModel, ICustomMeterDimensionsModel, ICustomMeterModel, IError, - IPlanModel, - IRestrictedUsersModel, + IPlanModel, IWebHookModel, Result } from "../../models"; import { Offers } from '../../shared/constants/infomessages'; import { useGlobalContext } from '../../shared/components/GlobalProvider'; import AlternateButton from "../../shared/components/AlternateButton"; import { - arrayItemErrorMessage, arrayItemErrorMessageFromForm, - arrayItemErrorMessageWithoutTouch, + arrayItemErrorMessage, arrayItemErrorMessageFromForm, handleSubmissionErrorsForForm } from "../../shared/formUtils/utils"; import { toast } from "react-toastify"; @@ -55,13 +51,13 @@ export type IPlanFormProps = { const ModifyPlan: React.FunctionComponent = (props) => { const { setFieldValue, values, handleChange, handleBlur, - touched, errors,handleSubmit, submitForm, setErrors, setSubmitting, dirty } = useFormikContext(); // formikProps + touched, errors,handleSubmit, submitForm, setErrors, setSubmitting } = useFormikContext(); // formikProps const globalContext = useGlobalContext(); const [loadingFormData, setLoadingFormData] = useState(true); const [disableValue, setDisableValue] = useState(true); const [formError, setFormError] = useState(null); - const [selectedKey, setSelectedKey] = useState('public'); + //const [selectedKey, setSelectedKey] = useState('public'); const OfferName = props.offerName as string; const PlanName = props.planName as string; const IsNew = props.isNew as boolean; @@ -219,19 +215,19 @@ const ModifyPlan: React.FunctionComponent = (props) => { } - const privateOnChange = (fieldKey: string, setFieldValue, ev?: React.SyntheticEvent, option?: IChoiceGroupOption) => { +// const privateOnChange = (fieldKey: string, setFieldValue, ev?: React.SyntheticEvent, option?: IChoiceGroupOption) => { - if (option) { - setSelectedKey(option.key); +// if (option) { +// //setSelectedKey(option.key); - if (option.key === 'private') { - setFieldValue(fieldKey, true, true); - } - else { - setFieldValue(fieldKey, false, true); - } - } - }; +// if (option.key === 'private') { +// setFieldValue(fieldKey, true, true); +// } +// else { +// setFieldValue(fieldKey, false, true); +// } +// } +// }; const selectOnChange = (fieldKey: string, setFieldValue, event: React.FormEvent, option?: IDropdownOption, index?: number) => { @@ -266,15 +262,15 @@ const ModifyPlan: React.FunctionComponent = (props) => { } }; - const handleAddUser = (arrayHelpers) => { - arrayHelpers.insert(arrayHelpers.form.values.plan.restrictedUsers.length, getInitialRestrictedUser()); - }; + // const handleAddUser = (arrayHelpers) => { + // arrayHelpers.insert(arrayHelpers.form.values.plan.restrictedUsers.length, getInitialRestrictedUser()); + // }; - const handleRemoveUser = (arrayHelpers, clientId) => { + // const handleRemoveUser = (arrayHelpers, clientId) => { - var idx = arrayHelpers.form.values.plan.restrictedUsers.findIndex(x => x.clientId === clientId); - arrayHelpers.form.setFieldValue(`plan.restrictedUsers.${idx}.isDeleted`, true, true); - }; + // var idx = arrayHelpers.form.values.plan.restrictedUsers.findIndex(x => x.clientId === clientId); + // arrayHelpers.form.setFieldValue(`plan.restrictedUsers.${idx}.isDeleted`, true, true); + // }; const handleAddCustomMeterDimension = (arrayHelpers) => { arrayHelpers.insert(arrayHelpers.form.values.plan.customMeterDimensions.length, getInitialCustomMeterDimension()); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/Plans.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/Plans.tsx index 29bbe2f..34c8257 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/Plans.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/Plans.tsx @@ -8,10 +8,10 @@ import ModifyPlan from "./ModifyPlan"; import {Formik} from "formik"; import {getInitialPlan, IPlanFormValues, planValidationSchema} from "./formUtils/planFormUtils"; import FormLabel from '../../shared/components/FormLabel'; -import {handleSubmissionErrorsForArray, handleSubmissionErrorsForForm} from "../../shared/formUtils/utils"; +import { handleSubmissionErrorsForForm} from "../../shared/formUtils/utils"; import {toast} from "react-toastify"; import { useGlobalContext } from '../../shared/components/GlobalProvider'; -import CustomMetersService from "../../services/MetersService"; +//import CustomMetersService from "../../services/MetersService"; const Plans: React.FunctionComponent = () => { @@ -27,7 +27,7 @@ const Plans: React.FunctionComponent = () => { const {offerName} = useParams(); const OfferName = offerName as string; - const PlanName = editPlanName as string; + //const PlanName = editPlanName as string; const globalContext = useGlobalContext(); const getPlans = async () => { @@ -249,7 +249,7 @@ const Plans: React.FunctionComponent = () => { for (let param of parametersToDelete) { var paramDeleteResult = await PlansService.deleteCustomMeterDimension(offerName as string, values.plan.planName, param.meterName); - var idx = values.plan.customMeterDimensions.findIndex(x => x.clientId === param.clientId); + //var idx = values.plan.customMeterDimensions.findIndex(x => x.clientId === param.clientId); //TODO: NEED TO HANDLE THE DISPLAY OF ERRORS FOR subkeys for forms if (!paramDeleteResult.success) { globalContext.hideProcessing(); @@ -271,7 +271,7 @@ const Plans: React.FunctionComponent = () => { param.annualUnlimited = param.monthlyUnlimited; var paramCreateResult = await PlansService.createOrUpdateCustomMeterDimension(offerName as string, param); - var idx1 = values.plan.customMeterDimensions.findIndex(x => x.clientId === param.clientId); + //var idx1 = values.plan.customMeterDimensions.findIndex(x => x.clientId === param.clientId); //TODO: NEED TO HANDLE THE DISPLAY OF ERRORS FOR subkeys for forms if (!paramCreateResult.success) { globalContext.hideProcessing(); @@ -291,7 +291,7 @@ const Plans: React.FunctionComponent = () => { param.annualUnlimited = param.monthlyUnlimited; var paramUpdateResult = await PlansService.createOrUpdateCustomMeterDimension(offerName as string, param); - var idx2 = values.plan.customMeterDimensions.findIndex(x => x.clientId === param.clientId); + //var idx2 = values.plan.customMeterDimensions.findIndex(x => x.clientId === param.clientId); //TODO: NEED TO HANDLE THE DISPLAY OF ERRORS FOR subkeys for forms if (!paramUpdateResult.success) { globalContext.hideProcessing(); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/RegExp.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/RegExp.tsx index 3c13e26..07c1285 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/RegExp.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/RegExp.tsx @@ -3,7 +3,7 @@ export const offerIdRegExp = /^[a-z0-9-_]+$/; export const httpURLRegExp = /^((?:https?\:\/\/)(?:[-a-z0-9]+\.)*[-a-z0-9]+.*)$/; export const iPAddressRegExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\/([0-9]|1[0-9]|2[0-9]|3[0-2])$/; export const emailRegExp = /((([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))[;]*)+/; -export const aplicationID_AADTenantRegExp = /^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/; +export const guidRegExp = /^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/; export const planIdRegExp = /^[a-z0-9-_]+$/; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/offerFormUtils.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/offerFormUtils.ts index 715a487..b9aa896 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/offerFormUtils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/offerFormUtils.ts @@ -1,9 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; import { ObjectSchema } from "yup"; import { IOfferModel } from "../../../models"; import { IOfferParameterModel } from "../../../models/IOfferParameterModel"; import { v4 as uuid } from "uuid"; -import { offerIdRegExp, emailRegExp, aplicationID_AADTenantRegExp } from "./RegExp"; +import { offerIdRegExp, emailRegExp, guidRegExp } from "./RegExp"; import { ErrorMessage } from "./ErrorMessage"; export const getInitialOfferParameter = (): IOfferParameterModel => { @@ -90,7 +93,7 @@ const offerValidator: ObjectSchema = yup.object().shape( offerAlias: yup.string() .required("Alias is a required field"), hostSubscription: yup.string() - .matches(aplicationID_AADTenantRegExp, + .matches(guidRegExp, { message: ErrorMessage.hostSubscription, excludeEmptyString: true diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/planFormUtils.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/planFormUtils.tsx index 9447765..0ac55de 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/planFormUtils.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/planFormUtils.tsx @@ -2,7 +2,7 @@ import * as yup from "yup"; import {ObjectSchema} from "yup"; import {ICustomMeterDimensionsModel, IPlanModel, IRestrictedUsersModel} from "../../../models"; import {v4 as uuid} from "uuid"; -import {aplicationID_AADTenantRegExp, planIdRegExp} from "./RegExp"; +import {guidRegExp, planIdRegExp} from "./RegExp"; import {ErrorMessage} from "./ErrorMessage"; export const getInitialPlan = (): IPlanFormValues => { @@ -100,7 +100,7 @@ const planValidator: ObjectSchema = yup.object().shape( is: (val) => { return !!val === false }, - then: yup.string().matches(aplicationID_AADTenantRegExp, + then: yup.string().matches(guidRegExp, { message: ErrorMessage.userId, excludeEmptyString: true diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/registerYupMethods.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/registerYupMethods.ts index f8dfd04..019a2fd 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/registerYupMethods.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Offers/formUtils/registerYupMethods.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; export const registerYupMethods = () => { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/AMLWorkSpace.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/AMLWorkSpace.tsx index c8556ad..b70d33f 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/AMLWorkSpace.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/AMLWorkSpace.tsx @@ -1,37 +1,39 @@ import React, { useEffect, useState } from 'react'; -import { useParams } from "react-router"; import { DefaultButton, + Dialog, + DialogFooter, + DialogType, FontIcon, - Label, - Link, - MessageBar, - MessageBarType, PrimaryButton, Stack, - TextField, - DialogFooter, - Dialog, - DialogType + TextField } from 'office-ui-fabric-react'; import FormLabel from "../../shared/components/FormLabel"; -import { FieldArray, Formik, useFormikContext } from "formik"; -import { handleSubmissionErrorsForForm } from "../../shared/formUtils/utils"; -import { IDeploymentsModel, IDeploymentVersionModel, IAMLWorkSpaceModel } from "../../models"; +import { Formik } from "formik"; +import { IAMLWorkSpaceModel } from "../../models"; import { Loading } from "../../shared/components/Loading"; import { useGlobalContext } from "../../shared/components/GlobalProvider"; import { toast } from "react-toastify"; -import { initialProductList } from './formUtils/ProductFormUtils'; -import ProductService from '../../services/ProductService'; -import { DialogBox } from '../../shared/components/Dialog'; import AlternateButton from '../../shared/components/AlternateButton'; -import { RouteComponentProps } from 'react-router-dom'; -import { initialAMLWorkSpaceList, IAMLWorkSpaceFormValues, initialAMLWorkSpaceFormValues, aMLWorkSpaceFormValidationSchema } from './formUtils/AMLWorkSpaceUtils'; +import { + aMLWorkSpaceFormValidationSchema, + IAMLWorkSpaceFormValues, + initialAMLWorkSpaceFormValues, + initialAMLWorkSpaceValues, + deleteAMLWorkSpaceValidator, +} from './formUtils/AMLWorkSpaceUtils'; +import { Hub } from "aws-amplify"; +import ProductService from "../../services/ProductService"; +import { handleSubmissionErrorsForForm } from "../../shared/formUtils/utils"; +import { ProductMessages } from '../../shared/constants/infomessages'; +import { DialogBox } from '../../shared/components/Dialog'; const AMLWorkSpace: React.FunctionComponent = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const globalContext = useGlobalContext(); - const [formError, setFormError] = useState(null); + //const [formError, setFormError] = useState(null); return ( { }} gap={15} > - { - debugger - globalContext.showProcessing(); - - setFormError(null); - - globalContext.hideProcessing(); - toast.success("Success!"); - setSubmitting(false); - setTimeout(() => { globalContext.setFormDirty(false); }, 500); - - }} - > - - + ); }; -export type IAMLWorkSpaceListProps = { -} +export type IAMLWorkSpaceListProps = {} export const AMLWorkSpaceList: React.FunctionComponent = (props) => { - const { values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty } = useFormikContext(); // formikProps - const { } = props; - let [workSpaceList, setworkSpaceList] = useState(); - let [workSpace, setworkSpace] = useState(initialAMLWorkSpaceFormValues); + //const { values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty } = useFormikContext(); // formikProps + //const { } = props; + let [workSpaceList, setWorkSpaceList] = useState(); + let [workSpace, setWorkSpace] = useState(initialAMLWorkSpaceFormValues); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let [workSpaceDeleteIndex, setworkSpaceDeleteIndex] = useState(0); const [loadingWorkSpace, setLoadingWorkSpace] = useState(false); const [formError, setFormError] = useState(null); const [workSpaceDialogVisible, setWorkSpaceDialogVisible] = useState(false); const [isDisplayDeleteButton, setDisplayDeleteButton] = useState(true); const [isEdit, setisEdit] = useState(true); - const { productId } = useParams(); - const globalContext = useGlobalContext(); - //Below code is for making design proper in Armtemplate page. - let body = (document.getElementsByClassName('App')[0] as HTMLElement); - - useEffect(() => { + const [AMLDeleteDialog, setAMLDeleteDialog] = useState(false); + const [selectedAML, setSelectedAML] = useState(initialAMLWorkSpaceValues); - getWorkSpaceList(); - return () => { - body.style.height = '100%'; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const globalContext = useGlobalContext(); const getWorkSpaceList = async () => { setLoadingWorkSpace(true); - // const results = await ProductService.getAmlWorkSpaceList(); - // if (results && results.value && results.success) { - // setworkSpaceList(results.value); - // if (results.value.length > 4) - // body.style.height = 'auto'; - // } - setworkSpaceList(initialAMLWorkSpaceList); - setLoadingWorkSpace(false); + const results = await ProductService.getAmlWorkSpaceList(); + if (results && results.value && results.success) { + setWorkSpaceList(results.value); + // if (results.value.length > 4) + // body.style.height = 'auto'; + // } + //setworkSpaceList(initialAMLWorkSpaceList); + setLoadingWorkSpace(false); + } else + toast.error('Failed to load AML Workspaces'); } const getFormErrorString = (touched, errors, property: string) => { return touched.aMLWorkSpace && errors.aMLWorkSpace && touched.aMLWorkSpace[property] && errors.aMLWorkSpace[property] ? errors.aMLWorkSpace[property] : ''; }; - const editWorkSpace = (Id: string): void => { - let editedWorkspace = initialAMLWorkSpaceList.filter(a => a.workspaceId == Id)[0]; - setworkSpace({ aMLWorkSpace: editedWorkspace }); + const getDeleteAMLErrorString = (touched, errors, property: string) => { + return (touched.selectedWorkspaceName && errors.selectedWorkspaceName && touched[property] && errors[property]) ? errors[property] : ''; + }; + + const editWorkSpace = async (workspaceName: string, idx: number) => { + + //let editedWorkspace = initialAMLWorkSpaceList.filter(a => a.workspaceName === Id)[0]; + let editedWorkspace = await ProductService.getAmlWorkSpaceByName(workspaceName); + if (editedWorkspace && editedWorkspace.value && editedWorkspace.success) { + setWorkSpace({ aMLWorkSpace: editedWorkspace.value }); + setworkSpaceDeleteIndex(idx); + } else + toast.error('Failed to load AML Workspaces'); + setisEdit(true); setDisplayDeleteButton(true); OpenWorkSpaceDialog(); - //history.push(WebRoute.ModifyProductInfo.replace(':productId', productId)); + //history.push(WebRoute.ModifyProductInfo.replace(':productName', productName)); }; const deleteWorkSpace = async (aMLWorkSpaceModelSelected: IAMLWorkSpaceModel) => { - // var deploymentDeleteResult = await ProductService.deleteWorkSpace(aMLWorkSpaceModelSelected.resourceId); - // if (handleSubmissionErrorsForForm(setErrors, setSubmitting, setFormError, 'deployment', deploymentDeleteResult)) { - // globalContext.hideProcessing(); - // return; - getWorkSpaceList(); + + setSelectedAML(aMLWorkSpaceModelSelected); + setAMLDeleteDialog(true); + }; const OpenNewWorkSpaceDialog = () => { - setworkSpace(initialAMLWorkSpaceFormValues); + setWorkSpace(initialAMLWorkSpaceFormValues); setisEdit(false); - setDisplayDeleteButton(false); + setDisplayDeleteButton(false); OpenWorkSpaceDialog(); } + const OpenWorkSpaceDialog = () => { setWorkSpaceDialogVisible(true); } @@ -143,10 +136,21 @@ export const AMLWorkSpaceList: React.FunctionComponent = setWorkSpaceDialogVisible(false); } + useEffect(() => { + + getWorkSpaceList(); + + Hub.listen('AMLWorkspaceNewDialog', (data) => { + OpenNewWorkSpaceDialog(); + }) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const WorkSpaceList = ({ amlWorkSpace }) => { if (!amlWorkSpace || amlWorkSpace.length === 0) { return - No Deployments + No AML Workspaces ; } else { return ( @@ -154,30 +158,24 @@ export const AMLWorkSpaceList: React.FunctionComponent = return ( - {value.workspaceId} + {value.workspaceName} - {value.resourceId} + {value.aadTenantId} - {value.aADApplicationId} + {value.resourceId} - {value.aADApplicationSecret} - - - { editWorkSpace(value.workspaceId) }} /> + { + editWorkSpace(value.workspaceName, idx) + }} /> {/* { deleteWorkSpace(value) }} /> */} @@ -189,48 +187,54 @@ export const AMLWorkSpaceList: React.FunctionComponent = } } + const CloseAMLDeleteDialog = () => { + setAMLDeleteDialog(false); + } + return ( -

Deployments

- +

AML + Workspaces

+
- - - - - - {loadingWorkSpace ? - ( - - - - ) - : - + + {loadingWorkSpace ? + ( + + + + ) + : - } + } + @@ -245,8 +249,7 @@ export const AMLWorkSpaceList: React.FunctionComponent = subText: { paddingTop: 0 }, - title: { - } + title: {} }, type: DialogType.normal, @@ -263,132 +266,226 @@ export const AMLWorkSpaceList: React.FunctionComponent = } }} > - <> - { - debugger - setFormError(null); - setSubmitting(true); - globalContext.showProcessing(); - - initialAMLWorkSpaceList.push( - { - aADApplicationId: values.aMLWorkSpace.aADApplicationId, - aADApplicationSecret: values.aMLWorkSpace.aADApplicationSecret, - resourceId: values.aMLWorkSpace.resourceId, - workspaceId: values.aMLWorkSpace.workspaceId, - isDeleted: false, - clientId: values.aMLWorkSpace.clientId - }) - - // var createWorkSpaceResult = await ProductService.createOrUpdateWorkSpace(values.aMLWorkSpace); - // if (handleSubmissionErrorsForForm(setErrors, setSubmitting, setFormError, 'deployment', createWorkSpaceResult)) { - // globalContext.hideProcessing(); - // return; - // } - - setSubmitting(false); + { + + setFormError(null); + setSubmitting(true); + globalContext.showProcessing(); + + //TODO: PUT THIS BACK IN + var createWorkSpaceResult = await ProductService.createOrUpdateWorkSpace(values.aMLWorkSpace); + if (handleSubmissionErrorsForForm(setErrors, setSubmitting, setFormError, 'aMLWorkSpace', createWorkSpaceResult)) { globalContext.hideProcessing(); - setisEdit(true); - setDisplayDeleteButton(true); - toast.success("Success!"); - getWorkSpaceList(); - CloseWorkSpaceDialog(); - }} - > - {({ handleChange, values, handleBlur, touched, errors, handleSubmit, submitForm, setFieldValue }) => ( -
- + + - - - + + - + + + +
- - - -
+ + + +
- { OpenNewWorkSpaceDialog() }} /> + { + OpenNewWorkSpaceDialog() + }} />
- - - - - - - - - - - - - - - - - -
- - WorkSpace Id -
- -
-
- - Resource Id -
- -
- -
- - AAD Application Id -
- -
- -
- - AADApplication Secret -
- -
-
- - - { - isDisplayDeleteButton ? - { - deleteWorkSpace(workSpace.aMLWorkSpace) - }} /> : null - } -
- )} -
- + return; + } + + setSubmitting(false); + + await getWorkSpaceList(); + globalContext.hideProcessing(); + toast.success("Success!"); + setisEdit(true); + setDisplayDeleteButton(true); + + Hub.dispatch( + 'AMLWorkspaceCreated', + { + event: 'WorkspaceCreated', + data: true, + message: '' + }); + + CloseWorkSpaceDialog(); + }} + > + {({ handleChange, values, handleBlur, touched, errors, handleSubmit, submitForm, setFieldValue }) => ( + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + {isDisplayDeleteButton && + { + deleteWorkSpace(workSpace.aMLWorkSpace) + }}> + Delete + + } +
+ + +
+
+
+ )} + + + { + CloseAMLDeleteDialog(); + }} + submitonClick={() => { + const btnsubmit = document.getElementById('btnAMLDelete') as HTMLButtonElement; + btnsubmit.click(); + }} + children={ + + { + + globalContext.showProcessing(); + var workspaceResult = await ProductService.deleteWorkSpace(selectedAML.workspaceName); + + if (handleSubmissionErrorsForForm((item) => { + }, (item) => { + }, setFormError, 'aMLWorkSpace', workspaceResult)) { + toast.error(formError); + globalContext.hideProcessing(); + return; + } + + await getWorkSpaceList(); + globalContext.hideProcessing(); + toast.success("AML Workspace Deleted Successfully!"); + + CloseWorkSpaceDialog(); + }} + > + {({ handleChange, values, handleBlur, touched, errors, handleSubmit }) => ( +
+ + + + + + + + + + +
+ Are you sure you want to delete version? +
+ { + + Type the workspace name +
+ +
+ } +
+
+ +
+
+ )} +
+
+ } />
); } diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Deployments.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Deployments.tsx index bba3e64..f148dad 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Deployments.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Deployments.tsx @@ -1,638 +1,1582 @@ import React, { useEffect, useState } from 'react'; import { useParams } from "react-router"; import { - DefaultButton, - FontIcon, - Label, - Link, - MessageBar, - MessageBarType, - PrimaryButton, - Stack, - TextField, - DialogFooter, - Dialog, - DialogType + ChoiceGroup, + DefaultButton, + Dialog, + DialogFooter, + DialogType, + Dropdown, + FontIcon, + IChoiceGroupOption, + IDropdownOption, + PrimaryButton, + Stack, + TextField } from 'office-ui-fabric-react'; import FormLabel from "../../shared/components/FormLabel"; -import { FieldArray, Formik, useFormikContext } from "formik"; +import { Formik, useFormikContext } from "formik"; import { handleSubmissionErrorsForForm } from "../../shared/formUtils/utils"; -import { IDeploymentsModel, IDeploymentVersionModel } from "../../models"; +import { IDeploymentsModel, IDeploymentVersionModel, IError } from "../../models"; import { Loading } from "../../shared/components/Loading"; import { useGlobalContext } from "../../shared/components/GlobalProvider"; import { toast } from "react-toastify"; -import { initialDeploymentList, initialDeploymentValues, deploymentFormValidationSchema, initialDeploymentFormValues, IDeploymentFormValues, initialDeploymentVersionList, initialVersionValues, versionFormValidationSchema } from './formUtils/ProductDetailsUtils'; -import { initialProductList } from './formUtils/ProductFormUtils'; +import { + deploymentFormValidationSchema, + getInitialDeployment, + getInitialVersion, + IDeploymentFormValues, + IDeploymentVersionFormValues, + initialDeploymentFormValues, + initialDeploymentList, + versionFormValidationSchema, + deletedeploymentValidator, + deleteVersionValidator, +} from './formUtils/ProductDetailsUtils'; import ProductService from '../../services/ProductService'; -import { DialogBox } from '../../shared/components/Dialog'; import AlternateButton from '../../shared/components/AlternateButton'; -import { RouteComponentProps } from 'react-router-dom'; - -const ProductDeployments: React.FunctionComponent = () => { - - const globalContext = useGlobalContext(); - const [formError, setFormError] = useState(null); - - return ( - - { - debugger - globalContext.showProcessing(); +import { Hub } from "aws-amplify"; +import { ProductMessages } from '../../shared/constants/infomessages'; +import { DialogBox } from '../../shared/components/Dialog'; +import * as yup from "yup"; - setFormError(null); +export type IProductDeploymentsProps = + { + productType: string; + } +const ProductDeployments: React.FunctionComponent = (props) => { - globalContext.hideProcessing(); - toast.success("Success!"); - setSubmitting(false); - setTimeout(() => { globalContext.setFormDirty(false); }, 500); + const globalContext = useGlobalContext(); - }} - > - - - - ); + const { productType } = props; + useEffect(() => { + }, []); + + return ( + + { + + globalContext.showProcessing(); + + //setFormError(null); + + globalContext.hideProcessing(); + toast.success("Success!"); + setSubmitting(false); + setTimeout(() => { + globalContext.setFormDirty(false); + }, 500); + + }} + > + + + + ); }; export type IDeploymentProps = { + productType: string } export const Deployments: React.FunctionComponent = (props) => { - const { values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty } = useFormikContext(); // formikProps - const { } = props; - let [deploymentList, setdeploymentList] = useState(); - let [deployment, setdeployment] = useState(initialDeploymentFormValues); - let [deploymentVersionList, setDeploymentVersionList] = useState([]); - const [loadingdeployment, setLoadingdeployment] = useState(false); - const [formError, setFormError] = useState(null); - const [deploymentDialogVisible, setDeploymentDialogVisible] = useState(false); - const [loadVersionForm, setloadVersionForm] = useState(false); - const [loadVersionData, setloadVersionData] = useState(false); - let [selectedVersion, setselectedVersion] = useState(initialVersionValues); - let [isNewVersionDisabled, setIsNewVersionDisabled] = useState(true); - const [isDisplayDeletedeploymentButton, setDisplayDeletedeploymentButton] = useState(true); - const [isEdit, setIsEdit] = useState(true); - - const { productId } = useParams(); - const globalContext = useGlobalContext(); - //Below code is for making design proper in Armtemplate page. - let body = (document.getElementsByClassName('App')[0] as HTMLElement); - - useEffect(() => { - globalContext.modifySaveForm(async () => { - await submitForm(); - }); - - getDeploymentsList(); - return () => { - body.style.height = '100%'; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const getDeploymentsList = async () => { - - setLoadingdeployment(true); - setloadVersionData(true); - // const results = await ProductService.getDeploymentList(); - // if (results && results.value && results.success) { - // setdeploymentList(results.value); - // if (results.value.length > 4) - // body.style.height = 'auto'; - // } - setdeploymentList(initialDeploymentList); - setloadVersionData(false); - setLoadingdeployment(false); - } + //const {values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty} = useFormikContext(); // formikProps + const { productType } = props; + let [deploymentList, setDeploymentList] = useState(); + let [deployment, setDeployment] = useState(initialDeploymentFormValues); + let [deploymentVersionList, setDeploymentVersionList] = useState([]); + const [loadingDeployment, setLoadingDeployment] = useState(false); + const [formError, setFormError] = useState(null); + const [deploymentDialogVisible, setDeploymentDialogVisible] = useState(false); + const [versionDialogVisible, setVersionDialogVisible] = useState(false); + let [selectedVersion, setSelectedVersion] = useState({ version: getInitialVersion() }); + let [isNewVersionDisabled, setIsNewVersionDisabled] = useState(true); + const [displayDeleteDeploymentButton, setDisplayDeleteDeploymentButton] = useState(true); + const [isEdit, setIsEdit] = useState(true); - const getFormErrorString = (touched, errors, property: string) => { - return touched.deployment && errors.deployment && touched.deployment[property] && errors.deployment[property] ? errors.deployment[property] : ''; - }; - - const editDeployment = (Id: string): void => { - let editeddeployment = initialDeploymentList.filter(a => a.deploymentId == Id)[0]; - let editeddeploymentVersionList = initialDeploymentVersionList.filter(a => a.deploymentId == Id); - setdeployment({ deployment: editeddeployment }); - setDisplayDeletedeploymentButton(true); - setIsEdit(true); - setDeploymentVersionList(editeddeploymentVersionList); - setIsNewVersionDisabled(false); - OpenDeploymentDialog(); - //history.push(WebRoute.ModifyProductInfo.replace(':productId', productId)); - }; - - const deleteDeployment = async (deploymentSelected: IDeploymentsModel) => { - var deploymentDeleteResult = await ProductService.deleteDeployment(deploymentSelected.productId, deploymentSelected.deploymentId); - // if (handleSubmissionErrorsForForm(setErrors, setSubmitting, setFormError, 'deployment', deploymentDeleteResult)) { - // globalContext.hideProcessing(); - // return; - - getDeploymentsList(); - }; - - const OpenNewDeploymentDialog = () => { - initialDeploymentFormValues.deployment.productId = productId as string; - setdeployment(initialDeploymentFormValues); - setDeploymentVersionList([]); - setDisplayDeletedeploymentButton(false); - setIsEdit(false) - setselectedVersion(initialVersionValues); - setIsNewVersionDisabled(true); - OpenDeploymentDialog(); - } - const OpenDeploymentDialog = () => { - setDeploymentDialogVisible(true); + const [deploytmentDeleteDialog, setDeploytmentDeleteDialog] = useState(false); + + const [selecteddeployment, setSelectedDeployment] = useState(getInitialDeployment); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [isVersionEdit, setIsVersionEdit] = useState(true); + + const { productName } = useParams(); + //const history = useHistory(); + const globalContext = useGlobalContext(); + //Below code is for making design proper in Armtemplate page. + let body = (document.getElementsByClassName('App')[0] as HTMLElement); + + useEffect(() => { + // globalContext.modifySaveForm(async () => { + // await submitForm(); + // }); + getDeploymentsList(); + return () => { + body.style.height = '100%'; } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - const CloseNewDeploymentDialog = () => { - setloadVersionForm(false); + const getDeploymentsList = async () => { + + setLoadingDeployment(true); + //setloadVersionData(true); + const results = await ProductService.getDeploymentListByProductName(productName as string); + if (results && results.value && results.success) { + setDeploymentList(results.value); + /*if (results.value.length > 4) + body.style.height = 'auto';*/ } + //setdeploymentList(initialDeploymentList); + //setloadVersionData(false); + setLoadingDeployment(false); + } + + const getFormErrorString = (touched, errors, property: string) => { + return touched.deployment && errors.deployment && touched.deployment[property] && errors.deployment[property] ? errors.deployment[property] : ''; + }; + + const getDeleteErrorString = (touched, errors, property: string) => { + return touched.selecteddeploymentName && errors && touched.selecteddeploymentName ? errors[property] : ''; + }; - const CloseDeploymentDialog = () => { - CloseNewDeploymentDialog(); - setDeploymentDialogVisible(false); + const editDeployment = async (Id: string) => { + // fetch our deployment and versions + + globalContext.showProcessing(); + const [ + deploymentResponse, + deploymentVersionsResponse + ] = await Promise.all([ + ProductService.getDeploymentByProductName(productName as string, Id), + ProductService.getDeploymentVersionListByDeploymentName(productName as string, Id) + ]); + globalContext.hideProcessing(); + + // var dataConnectorTypes: string[] = []; + // var telemetryDataConnectors: ITelemetryDataConnectorModel[] = []; + + if (deploymentResponse.success && deploymentVersionsResponse.success) { + + if (deploymentResponse.value) { + setDeployment({ deployment: deploymentResponse.value }); + } + + + if (deploymentVersionsResponse.value) + setDeploymentVersionList(deploymentVersionsResponse.value); + + setIsEdit(true); + setIsNewVersionDisabled(false); + setDisplayDeleteDeploymentButton(true); + OpenDeploymentDialog(); + } else { + let errorMessages: IError[] = []; + + errorMessages.concat(deploymentResponse.errors); + errorMessages.concat(deploymentVersionsResponse.errors); + + if (errorMessages.length > 0) { + toast.error(errorMessages.join(', ')); + } } + }; - const DeploymentList = ({ deployments, setFieldValue }) => { - if (!deployments || deployments.length === 0) { - return - No Deployments - ; - } else { - return ( - deployments.map((value: IDeploymentsModel, idx) => { - return ( - - - {value.deploymentId} - - - {value.versionId} - - - - { editDeployment(value.deploymentId) }} /> - {/* { deleteDeployment(value) }} /> */} - - - - ); - }) - ); + const getDeploymentVersionsList = async (deploymentName: string) => { + globalContext.showProcessing(); + let deploymentVersionsResponse = await ProductService.getDeploymentVersionListByDeploymentName(productName as string, deploymentName); + globalContext.hideProcessing(); - } + if (deploymentVersionsResponse.success) { + + if (deploymentVersionsResponse.value) + setDeploymentVersionList(deploymentVersionsResponse.value); + + } else { + let errorMessages: IError[] = []; + + errorMessages.concat(deploymentVersionsResponse.errors); + + if (errorMessages.length > 0) { + toast.error(errorMessages.join(', ')); + } } + } - return ( - - { - debugger; - }} - > - {/* */} - {({ isSubmitting, setFieldValue, values, handleChange, handleBlur, touched, errors, submitForm, dirty }) => { - return ( - -

Deployments

- - - - - - - - {loadingdeployment ? - ( - - - - ) - : - - - } - - - - - -
- - - - -
- - - -
- { OpenNewDeploymentDialog() }} /> -
+ const deleteDeployment = async (deploymentSelected: IDeploymentsModel) => { + // globalContext.showProcessing(); -
- ) - }} -
+ // // // determine if there are any versions for this deployment, if there are, prevent the deletion + // // var deploymentVersionsResponse = await ProductService.getDeploymentVersionListByDeploymentName(productName as string, deploymentSelected.deploymentName); + + // // if (deploymentVersionsResponse.success) { + // // if (deploymentVersionsResponse.value && deploymentVersionsResponse.value.length > 0) { + // // toast.error("You must delete all versions for the deployment first."); + // // globalContext.hideProcessing(); + // // return; + // // } + // // } + + // // var deleteResult = await ProductService.deleteDeployment(deploymentSelected.productName, deploymentSelected.deploymentName); + + // // if (handleSubmissionErrorsForForm((item) => { + // // }, (item) => { + // // }, setFormError, 'deployment', deleteResult)) { + // // toast.error(formError); + // // globalContext.hideProcessing(); + // // return; + // // } + + // await getDeploymentsList(); + // globalContext.hideProcessing(); + // toast.success("Deployment Deleted Successfully!"); + // CloseDeploymentDialog(); + setSelectedDeployment(deploymentSelected); + setDeploytmentDeleteDialog(true); + }; + + const OpenNewDeploymentDialog = () => { + let newDeployment = getInitialDeployment(); + newDeployment.productName = productName as string; + setDeployment({ deployment: newDeployment }); + setDeploymentVersionList([]); + setDisplayDeleteDeploymentButton(false); + setIsEdit(false) + setIsNewVersionDisabled(true); + OpenDeploymentDialog(); + } - -
- ); + +
+ +
+ + )} + + + } /> + + ); } export type IDeploymenVersionFormProps = { - formError?: string | null; - selectedVersion: IDeploymentVersionModel; - SetDeploymentDialogVisible: any; - setloadVersionForm: any; - setDeploymentVersionList: any; - deploymentVersionList: any; - deploymentId: string; + formError?: string | null; + selectedVersion: IDeploymentVersionFormValues; + hideVersionDialog: () => void; + refreshVersionList: () => void; + productType: string; + isNewVersion: boolean; } -export type IDeploymenListProps = { - deploymentList: IDeploymentVersionModel[]; - setloadVersionForm: any; - loadingVersion: boolean; - setDeploymentVersionList: any; - setloadVersionData: any; - setselectedVersion: any; - isNewVersionDisabled: boolean; +export type IDeploymentVersionListProps = { + deploymentVersionList: IDeploymentVersionModel[]; + productName: string; + productType: string; + selectedDeploymentName: string; + setDeploymentVersionList: any; + openVersionDialog: any; + setIsVersionEdit: any; + setSelectedVersion: any; + isNewVersionDisabled: boolean; } //#region Version export const VersionForm: React.FunctionComponent = (props) => { - const { values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty } = useFormikContext(); // formikProps - const { formError, SetDeploymentDialogVisible, selectedVersion, setloadVersionForm, setDeploymentVersionList, - deploymentVersionList, deploymentId } = props; + const { values, handleChange, handleBlur, touched, errors, handleSubmit, setFieldValue } = useFormikContext(); // formikProps + const { + hideVersionDialog, selectedVersion, isNewVersion, productType, refreshVersionList + } = props; + const [formError, setFormError] = useState(null); + const [authenticationTypes, setAuthenticationTypes] = useState([]); + const [amlWorkspaceDropdownOptions, setAMLWorkspaceDropdownOptions] = useState([]); + + const [sourceDropdownOptions, setSourceDropdownOptions] = useState([]); + const [publishedpipelineDropdownOptions, setPublishedpipelineDropdownOptions] = useState([]); + const [deployModelIdDropdownOptions, setDeployModelAPIDropdownOptions] = useState([]); + const [batchInferenceIdDropdownOptions, setBatchInferenceAPIDropdownOptions] = useState([]); + const [trainModelIdDropdownOptions, settrainModelIdDropdownOptions] = useState([]); + + + const [versionDeleteDialog, setVersionDeleteDialog] = useState(false); - let [version, setVersion] = useState(initialVersionValues); + const [selectedversion, setSelectedversion] = useState(getInitialVersion); + const [isAmlPipeline, setIsAmlPipeline] = useState(false); + const [isGitRepo, setIsGitRepo] = useState(false); + const [isUpload, setIsUpload] = useState(false); + const [isDisbalePipeLine, setIsDisbalePipeLine] = useState(true); + //let [version, setVersion] = useState(initialVersionValues); + const globalContext = useGlobalContext(); + let fileReader; - useEffect(() => { - setVersion(selectedVersion); - }, []); + const getSourceDropdownOptions = async () => { + // let workspaceOptions: IDropdownOption[] = []; + // workspaceOptions.push( + // { key: '', text: 'Select' }, + // { key: 'aml_pipelines', text: 'AML PipeLines' }, + // { key: 'git', text: 'GIT repo' }, + // { key: 'upload', text: 'Upload Project' }, + // ); + const results = await ProductService.getSourceModelList(); + if (results && results.value && results.success) { + let workspaceOptions: IDropdownOption[] = []; - const CloseForm = () => { - setloadVersionForm(false); + workspaceOptions.push( + { key: '', text: 'select' } + ); + + results.value.map((value, index) => { + workspaceOptions.push( + { key: value.id, text: value.displayName }, + ) + return workspaceOptions; + }); + setSourceDropdownOptions(workspaceOptions); } + else { + toast.error('Failed to load the Source options'); - const getVersionFormErrorString = (touched, errors, property: string) => { - return (touched.version && errors.version && touched.version[property] && errors.version[property]) ? errors.version[property] : ''; - }; + } + } - return ( - + const getAMLWorkspaceDropdownOptions = async () => { + // load the aml workspace dropdown results + const results = await ProductService.getAmlWorkSpaceList(); + if (results && results.value && results.success) { + let workspaceOptions: IDropdownOption[] = []; - { - debugger; - console.log("Version Form"); - - // deploymentVersionList.push( - // { - // versionId: values.deployment.versionId, - // apiAuthenticationKey: values.deployment.apiAuthenticationKey, - // realTimePredictApi: values.deployment.realTimePredictApi - // }); - // setDeploymentVersionList(deploymentVersionList); - // setloadVersionForm(false); - }} - > - {({ handleChange, values, handleBlur, touched, errors, handleSubmit }) => ( -
- New Versions - - - - - - - - - - - - - - - - - - -
- - - Id -
- -
-
- - ProductId: -
- -
-
- - API Authentication Key: -
- -
-
- - -
-
- )} -
-
- ); -} + workspaceOptions.push({ key: '', text: 'select' }); + results.value.map((value, index) => { + workspaceOptions.push( + { key: value.workspaceName, text: value.workspaceName }, + ) + return workspaceOptions; + }); + setAMLWorkspaceDropdownOptions(workspaceOptions); + } else + toast.error('Failed to load the AML Workspace options'); + } -export const VersionList: React.FunctionComponent = (props) => { - const { values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty } = useFormikContext(); // formikProps - const { setloadVersionData, setloadVersionForm, loadingVersion, - deploymentList, setDeploymentVersionList, setselectedVersion, isNewVersionDisabled } = props; + const getPublishedPipeLineDropdownOptions = async (workspaceName: string) => { + + const results = await ProductService.getPublishedPipeLineByAmlWorkSpaceList(workspaceName); + if (results && results.value && results.success) { + let workspaceOptions: IDropdownOption[] = []; + workspaceOptions.push({ key: '', text: 'select' }); + results.value.map((value, index) => { + workspaceOptions.push( + { key: value.id, text: value.displayName, title: value.description }, + ) + return workspaceOptions; + }); + setPublishedpipelineDropdownOptions(workspaceOptions); + setIsDisbalePipeLine(false); + } else + toast.error('Failed to load the AML Workspace options'); + } + + useEffect(() => { + + + console.log(values.version); + + let authTypes: IChoiceGroupOption[] = []; + if (productType === "RTP") { + authTypes.push({ key: 'Token', text: 'Token' }); + authTypes.push({ key: 'Key', text: 'Key' }); + authTypes.push({ key: 'None', text: 'None' }); + + } else if (productType === "BI") { + + } else { // train your own model - const OpenNewVersionDialog = () => { - setselectedVersion(initialVersionValues); - OpenVersionForm(); } - const OpenVersionForm = () => { - setloadVersionForm(true); + setAuthenticationTypes([...authTypes]); + + getAMLWorkspaceDropdownOptions(); + getSourceDropdownOptions(); + + if (values.version && values.version.amlWorkspaceName) { + getPublishedPipeLineDropdownOptions(values.version.amlWorkspaceName as string); + if (values.version.versionSourceType === 'amlPipeline') { + setIsAmlPipeline(true); + setIsGitRepo(false); + setIsUpload(false); + } + else if (values.version.versionSourceType === 'git') { + setIsGitRepo(true); + setIsAmlPipeline(false); + setIsUpload(false); + } + else if (values.version.versionSourceType === 'upload') { + setIsAmlPipeline(false); + setIsGitRepo(false); + setIsUpload(true); + } + } + + + Hub.listen('AMLWorkspaceCreated', (data) => { + console.log('captured workspace created in version form'); + getAMLWorkspaceDropdownOptions(); + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const getVersionFormErrorString = (touched, errors, property: string) => { + return (touched.version && errors.version && touched.version[property] && errors.version[property]) ? errors.version[property] : ''; + }; + + const getDeleteVersionFormErrorString = (touched, errors, property: string) => { + return (touched.selectedVersionName && errors.selectedVersionName && touched[property] && errors[property]) ? errors[property] : ''; + }; + + const DisplayErrors = (errors, values) => { + console.log('display errors:'); + console.log(errors); + console.log(values); + return null; + }; + + const deleteDeploymentVersion = async (versionSelected: IDeploymentVersionModel) => { + setSelectedversion(versionSelected); + setVersionDeleteDialog(true); + + // globalContext.showProcessing(); + // var deploymentDeleteVersionResult = await ProductService.deleteDeploymentVersion(versionSelected.productName, versionSelected.deploymentName, versionSelected.versionName); + + // if (handleSubmissionErrorsForForm((item) => { + // }, (item) => { + // }, setFormError, 'version', deploymentDeleteVersionResult)) { + // toast.error(formError); + // globalContext.hideProcessing(); + // return; + // } + + // await refreshVersionList(); + // globalContext.hideProcessing(); + // toast.success("Deployment Version Deleted Successfully!"); + // hideVersionDialog(); + }; + + const authenticationOnChange = (fieldKey: string, setFieldValue, ev?: React.SyntheticEvent, option?: IChoiceGroupOption) => { + + if (option) { + + setFieldValue(fieldKey, option.key, true); + } + }; + + const selectOnChange = (fieldKey: string, setFieldValue, event: React.FormEvent, option?: IDropdownOption, index?: number) => { + if (option) { + let key = (option.key as string); + setFieldValue(fieldKey, key, true); + + if (key === 'amlPipeline') { + setIsAmlPipeline(true); + setIsGitRepo(false); + setIsUpload(false); + } + else if (key === 'git') { + setIsGitRepo(true); + setIsAmlPipeline(false); + setIsUpload(false); + }/* zb: not handling upload for now + else if (key === 'upload') { + setIsAmlPipeline(false); + setIsGitRepo(false); + setIsUpload(true); + }*/ } + }; - const editVersionItem = (values, index) => { - setselectedVersion(values); - OpenVersionForm(); + const amlWorkspaceselectOnChange = (fieldKey: string, setFieldValue, event: React.FormEvent, option?: IDropdownOption, index?: number) => { + if (option) { + let key = (option.key as string); + setFieldValue(fieldKey, key, true); + + if (key == "") { + setFieldValue("version.batchInferenceId", "", true); + setFieldValue("version.trainModelId", "", true); + setFieldValue("version.deployModelId", "", true); + setIsDisbalePipeLine(true); + } + else { + getPublishedPipeLineDropdownOptions(option.key as string); + } } + }; + + const CloseDeploymentVersionDeleteDialog = () => { + setVersionDeleteDialog(false); + } - const deleteVersionItem = (values, index) => { - setloadVersionData(true); - deploymentList.splice(index); - setDeploymentVersionList(...deploymentList); - setloadVersionData(false); + const TemplateFileRead = (setFieldValue) => { + const content = fileReader.result; + setFieldValue(`values.version.projectFileContent`, content, true) + } + + const uploadfile = (event, idx, setFieldValue) => { + let file = event.target.files[0]; + if (file) { + setFieldValue('values.version.projectFileUrl', file.name, true) + if (file.type === "application/json") { + fileReader = new FileReader(); + fileReader.onloadend = (e) => { + TemplateFileRead(setFieldValue) + }; + fileReader.readAsText(file); + } + } else { + setFieldValue(`templates.${idx}.templateFilePath`, '', true) } + } + - return ( + return ( + +
+ - Versions - - - - - - - - - - - { - loadingVersion - ? - ( - - - - ) - : - deploymentList.map((value: IDeploymentVersionModel, idx) => { - return ( - - - - - - - ); - }) - } - - - - - - -
- VersionId - - APIs - - AuthenticationType - - Operations -
- - - -
- {value.versionId} - - {value.productID} - - {value.AuthenticationType} - - - { editVersionItem(value, idx) }} /> - { deleteVersionItem(value, idx) }} /> - -
- -
+ + + + + + + + + + { + productType === 'RTP' ? + + + + + + + + + + + { + authenticationOnChange('version.authenticationType', setFieldValue, event, option) + }} + className="defaultChoiceGroup" name="version.authenticationType" + selectedKey={values.version.authenticationType} + options={authenticationTypes} + /> + + {(values.version.authenticationType === 'Token' ? + + + + { + selectOnChange(`version.amlWorkspaceName`, setFieldValue, event, option, index) + }} + errorMessage={getVersionFormErrorString(touched, errors, 'amlWorkspaceName')} + defaultSelectedKey={values.version.amlWorkspaceName} + /> + { + + Hub.dispatch( + 'AMLWorkspaceNewDialog', + { + event: 'NewDialog', + data: true, + message: '' + }); + + }}>Create New + + + + : (values.version.authenticationType === 'Key' ? + + + + + : null) + )} + + + + + + : + + + + + { + selectOnChange(`version.versionSourceType`, setFieldValue, event, option, index) + }} + errorMessage={getVersionFormErrorString(touched, errors, 'versionSourceType')} + defaultSelectedKey={values.version.versionSourceType} + /> + + { + isGitRepo ? + + + + + + + + + + + + + + + + + + { + amlWorkspaceselectOnChange(`version.amlWorkspaceName`, setFieldValue, event, option, index) + }} + errorMessage={getVersionFormErrorString(touched, errors, 'amlWorkspaceName')} + defaultSelectedKey={values.version.amlWorkspaceName} + /> + { + + Hub.dispatch( + 'AMLWorkspaceNewDialog', + { + event: 'NewDialog', + data: true, + message: '' + }); + + }}>Create New + + + + : null + } + { + isAmlPipeline ? + + {(values.version.authenticationType === 'Token' ? + + + + { + amlWorkspaceselectOnChange(`version.amlWorkspaceName`, setFieldValue, event, option, index) + }} + errorMessage={getVersionFormErrorString(touched, errors, 'amlWorkspaceName')} + defaultSelectedKey={values.version.amlWorkspaceName} + /> + { + + Hub.dispatch( + 'AMLWorkspaceNewDialog', + { + event: 'NewDialog', + data: true, + message: '' + }); + + }}>Create New + + + + : (values.version.authenticationType === 'Key' ? + + + + + : null) + )} + + {productType === 'RTP' ? + + + + : } + {productType === 'TYOM' ? + isAmlPipeline ? + + + { + selectOnChange(`version.trainModelId`, setFieldValue, event, option, index) + }} + errorMessage={getVersionFormErrorString(touched, errors, 'trainModelId')} + defaultSelectedKey={values.version.trainModelId} disabled={isDisbalePipeLine} + /> + + : null + : } + {productType === 'BI' || productType === 'TYOM' ? + isAmlPipeline ? + + + { + selectOnChange(`version.batchInferenceId`, setFieldValue, event, option, index) + }} + errorMessage={getVersionFormErrorString(touched, errors, 'batchInferenceId')} + defaultSelectedKey={values.version.batchInferenceId} disabled={isDisbalePipeLine} + /> + + : null + : } + {productType === 'TYOM' ? + isAmlPipeline ? + + + { + selectOnChange(`version.deployModelId`, setFieldValue, event, option, index) + }} + errorMessage={getVersionFormErrorString(touched, errors, 'deployModelId')} + defaultSelectedKey={values.version.deployModelId} disabled={isDisbalePipeLine} + /> + + : null + : } + {productType === 'RTP' ? + + + { + authenticationOnChange('version.authenticationType', setFieldValue, event, option) + }} + className="defaultChoiceGroup" name="version.authenticationType" + selectedKey={values.version.authenticationType} + options={authenticationTypes} + /> + : } + + + : null + + } + { + isUpload + ? + + + + + + : null + } + + + + + + }
- ); + + + + {!isNewVersion && + { + deleteDeploymentVersion(selectedVersion.version) + }}> + Delete + + } +
+ + +
+
+ + + { + CloseDeploymentVersionDeleteDialog(); + }} + submitonClick={() => { + const btnsubmit = document.getElementById('btnVersionDeleteubmit') as HTMLButtonElement; + btnsubmit.click(); + }} + children={ + + { + + globalContext.showProcessing(); + var deploymentDeleteVersionResult = await ProductService.deleteDeploymentVersion(selectedversion.productName, selectedversion.deploymentName, selectedversion.versionName); + + if (handleSubmissionErrorsForForm((item) => { + }, (item) => { + }, setFormError, 'version', deploymentDeleteVersionResult)) { + toast.error(formError); + globalContext.hideProcessing(); + return; + } + + await refreshVersionList(); + globalContext.hideProcessing(); + toast.success("Deployment Version Deleted Successfully!"); + CloseDeploymentVersionDeleteDialog(); + hideVersionDialog(); + }} + > + {({ handleChange, values, handleBlur, touched, errors, handleSubmit }) => ( +
+ + + + + + + + + + +
+ Are you sure you want to delete version? +
+ { + + Type the version name +
+ +
+ } +
+
+ +
+
+ )} +
+
+ } /> +
+ ); +} + +export const VersionList: React.FunctionComponent = (props) => { + //const {values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty} = useFormikContext(); // formikProps + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { openVersionDialog, selectedDeploymentName, productName, productType, setIsVersionEdit, + deploymentVersionList, setSelectedVersion, isNewVersionDisabled } = props; + + //const globalContext = useGlobalContext(); + + const OpenNewVersionDialog = () => { + let v = getInitialVersion(); + v.deploymentName = selectedDeploymentName; + v.productName = productName; + setIsVersionEdit(false); + v.authenticationType = "Token"; + //TODO: confirm what the default authenticationtypes should be for the other product types + /*if (productType == "RTP") { + v.authenticationType = "Token"; + } else if (productType == "BI") { + v.authenticationType = "None"; + } else { // train your own model + v.authenticationType = "None"; + }*/ + + setSelectedVersion({ version: v }); + openVersionDialog(); + } + + const editVersionItem = (values, index) => { + setIsVersionEdit(true); + setSelectedVersion({ version: values }); + openVersionDialog(); + } + + return ( + +

Versions

+ + + + + + + + + + + { + deploymentVersionList.length === 0 + ? + + + : + deploymentVersionList.map((value: IDeploymentVersionModel, idx) => { + return ( + + + + + + + ); + }) + } + + + + + + +
+ Version Name + + APIs + + Authentication Type + + Operations +
+ No Data Exists +
+ {value.versionName} + + {value.productName} + + {value.authenticationType} + + + { + editVersionItem(value, idx) + }} /> + +
+ +
+
+ ); } //#endregion diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Info.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Info.tsx deleted file mode 100644 index 8b8ad76..0000000 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Info.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { MessageBar, MessageBarType, Stack, TextField, Dropdown, IDropdownOption, } from 'office-ui-fabric-react'; -import { useParams } from "react-router"; -import { Formik, useFormikContext } from "formik"; -import ProductService from "../../services/ProductService"; -import { Loading } from '../../shared/components/Loading'; -import { IProductModel } from "../../models"; -import { - initialInfoFormValues, IProductInfoFormValues, - productInfoValidationSchema, initialProductList, ProductType -} from "./formUtils/ProductFormUtils"; -import FormLabel from "../../shared/components/FormLabel"; -import { Products } from '../../shared/constants/infomessages'; -import { handleSubmissionErrorsForForm } from "../../shared/formUtils/utils"; -import { toast } from "react-toastify"; -import { useGlobalContext } from '../../shared/components/GlobalProvider'; - -const Info: React.FunctionComponent = () => { - - const { productId } = useParams(); - const globalContext = useGlobalContext(); - const [formState, setFormState] = useState(initialInfoFormValues); - const [loadingFormData, setLoadingFormData] = useState(true); - const [formError, setFormError] = useState(null); - - const getFormData = async (productId: string) => { - - setLoadingFormData(true); - // const productResponse = await ProductService.get(productId); - - // // Global errors should have already been handled for get requests by this point - // if (productResponse.value && productResponse.success) { - // var product = productResponse.value as IProductModel; - - // setFormState( - // { - // product: { ...product } - // }); - // } - - let product = initialProductList.filter(p => p.productId == productId)[0]; - setFormState( - { - product: { ...product } - }); - setLoadingFormData(false); - - } - - useEffect(() => { - if (productId) { - getFormData(productId); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - - if (loadingFormData) - return ( - - - - ); - - return ( - - { - - setFormError(null); - - setSubmitting(true); - globalContext.showProcessing(); - var updateProductResult = await ProductService.update(values.product); - if (handleSubmissionErrorsForForm(setErrors, setSubmitting, setFormError, 'product', updateProductResult)) { - globalContext.hideProcessing(); - return; - } - - setSubmitting(false); - globalContext.hideProcessing(); - - toast.success("Success!"); - - getFormData((productId as string)) - setTimeout(() => { globalContext.setFormDirty(false); }, 500); - - }} - > - - - - ); -}; - -export type IProductFormFormProps = { - isNew: boolean; - formError?: string | null; - products: IProductModel[]; -} -export const ProductForm: React.FunctionComponent = (props) => { - const { values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty, setFieldValue } = useFormikContext(); // formikProps - const { formError, isNew } = props; - - const globalContext = useGlobalContext(); - - useEffect(() => { - globalContext.modifySaveForm(async () => { - await submitForm(); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const getProductFormErrorString = (touched, errors, property: string, dirty) => { - globalContext.setFormDirty(dirty); - - return touched.product && errors.product && touched.product[property] && errors.product[property] ? errors.product[property] : ''; - }; - - const DisplayErrors = (errors) => { - return null; - }; - const getidlist = (): string => { - let idlist = '' - props.products.map((values, index) => { - idlist += values.productId + ','; - return idlist; - }) - values.product.Idlist = idlist.substr(0, idlist.length - 1); - return idlist.substr(0, idlist.length - 1); - } - - const selectOnChange = (fieldKey: string, event: React.FormEvent, option?: IDropdownOption, index?: number) => { - if (option) { - setFieldValue(fieldKey, option.key, false); - } - }; - - const textboxClassName = (props.isNew ? "form_textboxmodal" : "form_textbox"); - - return ( -
- {formError &&
-
-
} - - - {isNew && - - - - - - - - } - - - {/* */} - { - selectOnChange(`product.productType`, event, option, index) - }} - errorMessage={getProductFormErrorString(touched, errors, 'productType', dirty)} - defaultSelectedKey={values.product.productType} - /> - - - - {/* */} - - { - selectOnChange(`product.hostType`, event, option, index) - }} - errorMessage={getProductFormErrorString(touched, errors, 'hostType', dirty)} - defaultSelectedKey={values.product.productType} - /> - - - - - - - -
- ); -} - -export default Info; \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/ProductDetail.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/ProductDetail.tsx index 2430d0b..2c001d0 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/ProductDetail.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/ProductDetail.tsx @@ -1,145 +1,38 @@ -import React, { } from 'react'; +import React, { useEffect, useState } from 'react'; import { Stack } from 'office-ui-fabric-react'; -import { RouteComponentProps } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import ProductDeployments from '../Products/Deployments'; -import AMLWorkSpace from '../Products/AMLWorkSpace' +import AMLWorkSpace from '../Products/AMLWorkSpace' +import ProductService from '../../services/ProductService'; const ProductDetail: React.FunctionComponent = () => { - //#region AMLWork/space - // const AmlWorkSpace = () => { - // const globalContext = useGlobalContext(); - // return ( - // { - // const input = {...values}; - // setSubmitting(true); - // globalContext.showProcessing(); - - // for (let param of input.templateParameters) { - - // var idx = values.templateParameters.findIndex(x => x.clientId === param.clientId); - // let paramUpdateResult = await ArmTemplateParameterService.update(offerName as string, param); - // if (handleSubmissionErrorsForArray(setErrors, setSubmitting, setFormError, 'templateParameters', idx, paramUpdateResult)) { - // globalContext.hideProcessing(); - // return; - // } - // } - - // setSubmitting(false); - // globalContext.hideProcessing(); - - // toast.success("Success!"); - - // getArmTemplateParameters(); - // setTimeout(() => {globalContext.setSecondaryFormDirty(false);}, 500); - // }} - // > - // - // - // ); - - // } - - // type IArmTemplateParametersFormBodyProps = { - // formError?: string | null; - // } - - // const ArmTemplateParametersFormBody: React.FunctionComponent = (props) => { - // const {values, handleChange, handleBlur, touched, errors,handleSubmit, submitForm, dirty} = useFormikContext(); // formikProps - - // const globalContext = useGlobalContext(); - // const {formError} = props; - - // useEffect(() => { - // globalContext.modifySaveForm(async () => { - // await submitForm(); - // }); - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, []); - - - // return ( - //
- //

Parameters

- // {formError &&
- //
- //
} - - // - // - // - // - // - // - // - // - // {values.templateParameters.length > 0 ? - // - // { - // return ( - // - // - // {errors && typeof errors.templateParameters === 'string' ? - //
{errors.templateParameters}
: null} - // {values.templateParameters.map((value: IARMTemplateParameterModel, idx) => { - // return ( - // - // - // - // - // - // ); - // })} - // - // - // ); - // }} - // /> - // - // : - // - // - // - // - // - // } - //
- // - // - // - // - // - //
- // {value.name} - // - // {value.type} - // - // - //
- // No Data Found - //
- //
- // ); - // } - //#endregion + const { productName } = useParams(); + const [productType, setProductType] = useState(""); + const [loading, setloading] = useState(true); + + const getProduct = async () => { + setloading(true); + const results = await ProductService.get(productName as string); + if (results && !results.hasErrors && results.value) + setProductType(results.value.productType); + else { + if (results.hasErrors) { + // TODO: display errors + alert(results.errors.join(', ')); + } + } + + setloading(false); + } + useEffect(() => { + getProduct(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( { verticalFill styles={{ root: { - width: '90%', + width: '100%', margin: '0 auto', textAlign: 'center', color: '#605e5c' @@ -156,9 +49,14 @@ const ProductDetail: React.FunctionComponent = () => { }} gap={15} > - - - + {!loading ? + + + + + : null + } + ); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Products.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Products.tsx index 7b6cf40..6783eef 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Products.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/Products.tsx @@ -1,30 +1,151 @@ import React, { useEffect, useState } from 'react'; import { - Stack, - PrimaryButton, + Dialog, + DialogFooter, + DialogType, + Dropdown, + FontIcon, + IDropdownOption, MessageBar, MessageBarType, - Dialog, DialogType, DialogFooter, - FontIcon, - TextField, + PrimaryButton, + Stack, + TextField } from 'office-ui-fabric-react'; import FormLabel from "../../shared/components/FormLabel"; import { useHistory } from "react-router"; import { WebRoute } from "../../shared/constants/routes"; -import { Result, IProductModel } from '../../models'; +import { IError, IProductModel } from '../../models'; import { Loading } from "../../shared/components/Loading"; import AlternateButton from "../../shared/components/AlternateButton"; -import { - initialInfoFormValues, IProductInfoFormValues, - productInfoValidationSchema, initialProductValues, deleteProductValidator, initialProductList -} from "./formUtils/ProductFormUtils"; -import { Formik } from "formik"; +import { initialInfoFormValues, IProductInfoFormValues, productInfoValidationSchema } from "./formUtils/ProductFormUtils"; +import { Formik, useFormikContext } from "formik"; import { useGlobalContext } from "../../shared/components/GlobalProvider"; import { toast } from "react-toastify"; import { handleSubmissionErrorsForForm } from "../../shared/formUtils/utils"; -import { DialogBox } from '../../shared/components/Dialog'; -import { ProductForm } from './Info'; import ProductService from '../../services/ProductService'; +import { ProductMessages } from '../../shared/constants/infomessages'; +import {CopyToClipboard} from 'react-copy-to-clipboard'; +import ReactHtmlParser from 'react-html-parser'; + +export type IProductFormFormProps = { + isNew: boolean; + formError?: string | null; + products: IProductModel[]; + productTypes: IDropdownOption[]; + hostTypes: IDropdownOption[]; +} + +export const ProductForm: React.FunctionComponent = (props) => { + const { values, handleChange, handleBlur, touched, errors, handleSubmit, submitForm, dirty, setFieldValue } = useFormikContext(); // formikProps + const { formError, isNew, productTypes, hostTypes } = props; + + const globalContext = useGlobalContext(); + + useEffect(() => { + globalContext.modifySaveForm(async () => { + await submitForm(); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const getProductFormErrorString = (touched, errors, property: string, dirty) => { + setTimeout(() => { globalContext.setFormDirty(dirty); }, 500); + + return touched.product && errors.product && touched.product[property] && errors.product[property] ? errors.product[property] : ''; + }; + + const DisplayErrors = (errors) => { + return null; + }; + const getidlist = (): string => { + let idlist = '' + props.products.map((values, index) => { + idlist += values.productName + ','; + return idlist; + }) + values.product.Idlist = idlist.substr(0, idlist.length - 1); + return idlist.substr(0, idlist.length - 1); + } + + const selectOnChange = (fieldKey: string, event: React.FormEvent, option?: IDropdownOption, index?: number) => { + if (option) { + setFieldValue(fieldKey, option.key, false); + } + }; + + const textboxClassName = (props.isNew ? "form_textboxmodal" : "form_textbox"); + + return ( +
+ {formError &&
+
+
} + + + {isNew && + + + + + + + + } + + + { + selectOnChange(`product.productType`, event, option, index) + }} + errorMessage={getProductFormErrorString(touched, errors, 'productType', dirty)} + defaultSelectedKey={values.product.productType} + /> + + + + { + selectOnChange(`product.hostType`, event, option, index) + }} + errorMessage={getProductFormErrorString(touched, errors, 'hostType', dirty)} + defaultSelectedKey={values.product.productType} + /> + + + + + + +
+ ); +} const Products: React.FunctionComponent = () => { const history = useHistory(); @@ -35,44 +156,77 @@ const Products: React.FunctionComponent = () => { const [products, setProducts] = useState([]); const [loadingProducts, setLoadingProducts] = useState(true); const [productDialogVisible, setProductDialogVisible] = useState(false); - const [productDeleteDialog, setProductDeleteDialog] = useState(false); - const [selectedProduct, setselectedProduct] = useState(initialProductValues); + const [productTypeDropdownOptions, setProductTypeDropdownOptions] = useState([]); + const [hostTypeDropdownOptions, setHostTypeDropdownOptions] = useState([]); + const [LunaWebhookUrlv2DialogVisible, setLunaWebhookUrlv2DialogVisible] = useState(false); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [formError, setFormError] = useState(null); const getProducts = async () => { + globalContext.showProcessing(); setLoadingProducts(true); - // const results = await ProductService.list(); - // if (results && !results.hasErrors && results.value) - // setProducts(results.value); - // else { - // setProducts([]); - // if (results.hasErrors) { - // // TODO: display errors - // alert(results.errors.join(', ')); - // } - // } - - setProducts(initialProductList); - + const [ + productResponse, + productTypeResponse, + hostTypeResponse + ] = await Promise.all([ + await ProductService.list(), + ProductService.getProductTypes(), + ProductService.getHostTypes() + ]); setLoadingProducts(false); + globalContext.hideProcessing(); + + if (productResponse.success && productTypeResponse.success && hostTypeResponse.success) { + + if (productResponse.value) + setProducts(productResponse.value); + else + setProducts([]); + + let productTypeOptions: IDropdownOption[] = []; + productTypeOptions.push({ key: '', text: 'Select...' }); + + if (productTypeResponse.value) { + productTypeResponse.value.map((value, index) => { + productTypeOptions.push( + { key: value.id, text: value.displayName }, + ) + return productTypeResponse; + }); + } + setProductTypeDropdownOptions(productTypeOptions); + + let hostTypeOptions: IDropdownOption[] = []; + hostTypeOptions.push({ key: '', text: 'Select...' }); + + if (hostTypeResponse.value) { + hostTypeResponse.value.map((value, index) => { + hostTypeOptions.push( + { key: value.id, text: value.displayName }, + ) + return hostTypeResponse; + }); + } + setHostTypeDropdownOptions(hostTypeOptions); + + } else { + let errorMessages: IError[] = []; + + errorMessages.concat(productResponse.errors); + errorMessages.concat(productTypeResponse.errors); + errorMessages.concat(hostTypeResponse.errors); + + if (errorMessages.length > 0) { + toast.error(errorMessages.join(', ')); + } + } } - const editItem = (productId: string): void => { - // history.push(WebRoute.ModifyProductInfo.replace(':productId', productId)); - - history.push(WebRoute.ProductDetail.replace(':productId', productId)); - // let product = initialProductList.filter(p => p.productId == productId)[0]; - // setFormState( - // { - // product: { ...product } - // }); - // setProductDialogVisible(true); - }; - const deleteItem = (productSelected: IProductModel, idx: number): void => { - productSelected.selectedProductindex = idx; - setselectedProduct(productSelected); - setProductDeleteDialog(true); + const editItem = (productName: string): void => { + history.push(WebRoute.ProductDetail.replace(':productName', productName)); }; const Products = ({ products }) => { @@ -86,7 +240,7 @@ const Products: React.FunctionComponent = () => { return ( - {value.productId} + {value.productName} {value.productType} @@ -109,8 +263,7 @@ const Products: React.FunctionComponent = () => { }, }} > - { editItem(value.productId) }} /> - { deleteItem(value, idx) }} /> + { editItem(value.productName) }} /> @@ -129,28 +282,24 @@ const Products: React.FunctionComponent = () => { setProductDialogVisible(false); }; - const CloseProductDeleteDialog = () => { - setProductDeleteDialog(false); - } - const handleFormSubmission = async (e) => { if (globalContext.saveForm) await globalContext.saveForm(); }; - const handleSubmissionErrors = (result: Result, setSubmitting: any): boolean => { - if (result.hasErrors) { - // TODO - display the errors here - alert(result.errors.join(', ')); - setSubmitting(false); - return true; - } - return false; - } + // const handleSubmissionErrors = (result: Result, setSubmitting: any): boolean => { + // if (result.hasErrors) { + // // TODO - display the errors here + // alert(result.errors.join(', ')); + // setSubmitting(false); + // return true; + // } + // return false; + // } - const getFormErrorString = (touched, errors, property: string) => { - return touched && errors && touched[property] && errors[property] ? errors[property] : ''; - }; + // const getFormErrorString = (touched, errors, property: string) => { + // return touched && errors && touched[property] && errors[property] ? errors[property] : ''; + // }; const showNewProductDialog = (): void => { setProductDialogVisible(true); @@ -160,6 +309,14 @@ const Products: React.FunctionComponent = () => { showNewProductDialog(); }; + const hideLunaWebhookUrlv2Dialog = (): void => { + setLunaWebhookUrlv2DialogVisible(false); + }; + + const showLunaWebhookUrlv2Dialog = (): void => { + setLunaWebhookUrlv2DialogVisible(true); + }; + return ( { > - + @@ -266,10 +423,10 @@ const Products: React.FunctionComponent = () => { globalContext.hideProcessing(); toast.success("Success!"); if (CreateProductResult.value != null) - history.push(WebRoute.ProductDetail.replace(':productId', CreateProductResult.value.productId)); + history.push(WebRoute.ProductDetail.replace(':productName', CreateProductResult.value.productName)); }} > - + { text="Save" /> +
- - - - - - - - - - - - - - - - -
- Are you sure you want to delete this product ? -
- Product ID: - - {values.productId} -
- Owner: - - {values.owner} -
- { - - Type the product id -
- -
- } -
-
- -
- - )} - - - } /> + > + +
+ + +
+
+ +
+
+ + { + let class1 = document.getElementsByClassName('unSubscribewebhookURL')[0] as HTMLElement; + let class2 = document.getElementsByClassName('suspendwebhookURL')[0] as HTMLElement; + class1.className = class1.className.replace('copied', ''); + class2.className = class2.className.replace('copied', ''); + + let copied = document.getElementsByClassName('subscribewebhookURL')[0] as HTMLElement; + copied.className = copied.className + " copied"; + toast.success("Copied !"); + }} /> + +
+
+
+ + + +
+
+ +
+
+ + { + let class1 = document.getElementsByClassName('subscribewebhookURL')[0] as HTMLElement; + let class2 = document.getElementsByClassName('suspendwebhookURL')[0] as HTMLElement; + class1.className = class1.className.replace('copied', ''); + class2.className = class2.className.replace('copied', ''); + + let copied = document.getElementsByClassName('unSubscribewebhookURL')[0] as HTMLElement; + copied.className = copied.className + " copied"; + toast.success("Copied !"); + }} /> + +
+
+
+ + +
+
+ +
+
+ + { + let class1 = document.getElementsByClassName('subscribewebhookURL')[0] as HTMLElement; + let class2 = document.getElementsByClassName('unSubscribewebhookURL')[0] as HTMLElement; + class1.className = class1.className.replace('copied', ''); + class2.className = class2.className.replace('copied', ''); + + let copied = document.getElementsByClassName('suspendwebhookURL')[0] as HTMLElement; + copied.className = copied.className + " copied"; + toast.success("Copied !"); + }} /> + +
+
+
+ +
+
+ ); } diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/AMLWorkSpaceUtils.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/AMLWorkSpaceUtils.ts index e586d1f..65e7b23 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/AMLWorkSpaceUtils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/AMLWorkSpaceUtils.ts @@ -1,9 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; import { ObjectSchema } from "yup"; -import {IAMLWorkSpaceModel } from "../../../models"; +import { IAMLWorkSpaceModel } from "../../../models"; import { v4 as uuid } from "uuid"; -import { workSpaceIdRegExp } from "./RegExp"; +import { workSpaceNameRegExp } from "./RegExp"; import { ErrorMessage } from "./ErrorMessage"; +import { guidRegExp } from "../../Offers/formUtils/RegExp"; export const shallowCompare = (obj1, obj2) => Object.keys(obj1).length === Object.keys(obj2).length && @@ -12,36 +16,18 @@ export const shallowCompare = (obj1, obj2) => ); export const initialAMLWorkSpaceValues: IAMLWorkSpaceModel = { - aADApplicationId: '', - aADApplicationSecret: '', + aadApplicationId: '', + aadTenantId: '', + aadApplicationSecrets: '', resourceId: '', - workspaceId: '', + registeredTime: new Date().toLocaleString(), + workspaceName: '', isSaved: false, isModified: false, - clientId: uuid() + clientId: uuid(), + selectedWorkspaceName: '', }; -export let initialAMLWorkSpaceList: IAMLWorkSpaceModel[] = [{ - aADApplicationId: '1', - aADApplicationSecret: '1', - resourceId: '1', - workspaceId: '1', - isDeleted: false, - isSaved: false, - isModified: false, - clientId: uuid() -}, -{ - aADApplicationId: '2', - aADApplicationSecret: '2', - resourceId: '2', - workspaceId: '2', - isDeleted: false, - isSaved: false, - isModified: false, - clientId: uuid() -}]; - export interface IAMLWorkSpaceFormValues { aMLWorkSpace: IAMLWorkSpaceModel; } @@ -53,15 +39,54 @@ export const initialAMLWorkSpaceFormValues: IAMLWorkSpaceFormValues = { const aMLWorkSpaceValidator: ObjectSchema = yup.object().shape( { clientId: yup.string(), - aADApplicationId: yup.string(), - aADApplicationSecret: yup.string(), - resourceId: yup.string(), - workspaceId: yup.string() - .matches(workSpaceIdRegExp, + aadApplicationId: yup.string().matches(guidRegExp, + { + message: ErrorMessage.aadApplicationId, + excludeEmptyString: true + }) + .required('AAD Application Id is required'), + aadTenantId: yup.string().matches(guidRegExp, { - message: ErrorMessage.workSpaceID, + message: ErrorMessage.tenantId, excludeEmptyString: true - }).required("WorkspaceId is required"), + }) + .required('Tenant Id is required'), + aadApplicationSecrets: yup.string(), + resourceId: yup.string(), + registeredTime: yup.string(), + workspaceName: yup.string() + .matches(workSpaceNameRegExp, + { + message: ErrorMessage.workSpaceName, + excludeEmptyString: true + }).required("Workspace Name is required"), + selectedWorkspaceName: yup.string() + } +); + +export const deleteAMLWorkSpaceValidator: ObjectSchema = yup.object().shape( + { + clientId: yup.string(), + aadApplicationId: yup.string(), + aadTenantId: yup.string(), + aadApplicationSecrets: yup.string(), + resourceId: yup.string(), + registeredTime: yup.string(), + workspaceName: yup.string(), + selectedWorkspaceName: yup.string() + .matches(workSpaceNameRegExp, + { + message: ErrorMessage.workSpaceName, + excludeEmptyString: true + }) + .test('selectedWorkspaceName', 'WorkSpace name does not match', function (value: string) { + const name: string = this.parent.workspaceName; + if (!value) + return true; + + return value.toLowerCase() === name.toLowerCase(); + }) + .required("Workspace Name is required"), } ); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ErrorMessage.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ErrorMessage.tsx index 4772c03..3dfa2ac 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ErrorMessage.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ErrorMessage.tsx @@ -1,14 +1,16 @@ export const ErrorMessage = { - productID :"An product ID must consist only of lowercase, alphanumeric characters, dashes or underscores", - deploymentID :"An deployment ID must consist only of lowercase, alphanumeric characters, dashes or underscores", - versionID :"An version ID must consist only of lowercase, alphanumeric characters, dashes or underscores", - workSpaceID :"An offer ID must consist only of lowercase, alphanumeric characters, dashes or underscores", + productName :"An product name must consist only of lowercase, alphanumeric characters, dashes or underscores", + deploymentName :"An deployment name must consist only of lowercase, alphanumeric characters, dashes or underscores", + versionName :"A deployment version name must consist only of lowercase, alphanumeric characters, dashes or underscores", + workSpaceName :"An offer ID must consist only of lowercase, alphanumeric characters, dashes or underscores", parameterName :"Parameter Name must consist only of lowercase, alphanumeric characters, dashes or underscores", hostSubscription:"Host subscription ID must be in valid GUID format", Email :"Must be valid emails. Must be separated by semi-colons.For example, rtoni@toolsgrou.com; jyousef@toolsgrou.com", IpAddress:"IP Blocks must be in valid format (i.e: 172.16.3.0/24)", httpUrl:"Invalid URL format", userId:"Tenant ID must be in valid GUID format", + aadApplicationId:"AAD Application ID must be in valid GUID format", + tenantId:"Tenant ID must be in valid GUID format", planID :"An plan ID must consist only of lowercase, alphanumeric characters, dashes or underscores", } diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductDetailsUtils.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductDetailsUtils.ts index 8624754..78437b6 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductDetailsUtils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductDetailsUtils.ts @@ -1,8 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; import { ObjectSchema } from "yup"; import { IDeploymentsModel, IDeploymentVersionModel } from "../../../models"; import { v4 as uuid } from "uuid"; -import { deploymentIdRegExp, versionIdRegExp } from "./RegExp"; +import { deploymentNameRegExp, versionNameRegExp } from "./RegExp"; import { ErrorMessage } from "./ErrorMessage"; export const shallowCompare = (obj1, obj2) => @@ -11,31 +14,47 @@ export const shallowCompare = (obj1, obj2) => obj2.hasOwnProperty(key) && obj1[key] === obj2[key] ); -export const initialDeploymentValues: IDeploymentsModel = { - productId: '', - versionId: '', - deploymentId: '', - description: '', - isSaved: false, - isModified: false, - clientId: uuid() +export const getInitialDeployment = (): IDeploymentsModel => { + return { + productName: '', + selecteddeploymentName:'', + versionName: '', + deploymentName: '', + description: '', + isSaved: false, + isModified: false, + clientId: uuid() + } }; -export const initialVersionValues: IDeploymentVersionModel = { - deploymentId: '', - productID: '', - AMLWorkspaceId: '', - AuthenticationType: '', - BatchInferenceAPI: '', - DeployModelAPI: '', - trainModelApi: '', - versionId: '' +export const getInitialVersion = (): IDeploymentVersionModel => { + return { + productName: '', + deploymentName: '', + versionName: '', + realTimePredictAPI: '', + trainModelId: '', + batchInferenceId: '', + deployModelId: '', + authenticationType: '', + authenticationKey: '', + amlWorkspaceName: '', + advancedSettings: '', + selectedVersionName:'', + versionSourceType:'', + gitUrl:'', + gitPersonalAccessToken :'', + gitVersion:'', + projectFileUrl:'', + projectFileContent:'' + } }; export const initialDeploymentList: IDeploymentsModel[] = [{ - productId: '1', - deploymentId: '1', - versionId: '1.0', + productName: 'a1', + selecteddeploymentName:'', + deploymentName: 'd1', + versionName: '1.0', description: '', isDeleted: false, isSaved: false, @@ -43,9 +62,10 @@ export const initialDeploymentList: IDeploymentsModel[] = [{ clientId: uuid() }, { - productId: '1', - deploymentId: '2', - versionId: '2.0', + productName: 'b1', + selecteddeploymentName:'', + deploymentName: 'd2', + versionName: '2.0', description: '', isDeleted: false, isSaved: false, @@ -53,85 +73,120 @@ export const initialDeploymentList: IDeploymentsModel[] = [{ clientId: uuid() }]; -export const initialDeploymentVersionList: IDeploymentVersionModel[] = [ - { - deploymentId: '1', - versionId: '1.0', - productID: 'realTimePredictApi', - AMLWorkspaceId: '', - AuthenticationType: '', - BatchInferenceAPI: '', - DeployModelAPI: '', - trainModelApi: '' - }, - { - deploymentId: '1', - versionId: '2.0', - productID: 'realTimePredictApi', - AMLWorkspaceId: '', - AuthenticationType: '', - BatchInferenceAPI: '', - DeployModelAPI: '', - trainModelApi: '' - }, - { - deploymentId: '1', - versionId: '3.0', - productID: 'realTimePredictApi', - AMLWorkspaceId: '', - AuthenticationType: '', - BatchInferenceAPI: '', - DeployModelAPI: '', - trainModelApi: '' - } -]; - export interface IDeploymentFormValues { deployment: IDeploymentsModel; } -export interface IVersionFormValues { +export interface IDeploymentVersionFormValues { version: IDeploymentVersionModel; } export const initialDeploymentFormValues: IDeploymentFormValues = { - deployment: initialDeploymentValues + deployment: getInitialDeployment() } const deploymentValidator: ObjectSchema = yup.object().shape( { clientId: yup.string(), - productId: yup.string(), - versionId: yup.string(), - deploymentId: yup.string() - .required("Id is a required field") - .matches(deploymentIdRegExp, - { - message: ErrorMessage.deploymentID, - excludeEmptyString: true - }), - description: yup.string() + productName: yup.string(), + selecteddeploymentName:yup.string(), + versionName: yup.string(), + deploymentName: yup.string() + .required("Id is a required field") + .matches(deploymentNameRegExp, + { + message: ErrorMessage.deploymentName, + excludeEmptyString: true + }), + description: yup.string(), + } +); + +export const deletedeploymentValidator: ObjectSchema = yup.object().shape( + { + + clientId: yup.string(), + productName: yup.string(), + selecteddeploymentName:yup.string() + .test('selecteddeploymentName', 'Deployment name does not match', function (value: string) { + const name: string = this.parent.deployment.deploymentName; + if (!value) + return true; + + return value.toLowerCase() === name.toLowerCase(); + }).required("Deployment Name is a required field"), + versionName: yup.string(), + deploymentName: yup.string(), + description: yup.string(), + } ); const versionFormValidator: ObjectSchema = yup.object().shape( { - deploymentId: yup.mixed().notRequired(), + deploymentName: yup.mixed().notRequired(), deploymentVersionList: yup.array(), - versionId: yup.string() - .matches(versionIdRegExp, - { - message: ErrorMessage.versionID, - excludeEmptyString: true - }) - .required("VersionId is a required field"), - productID: yup.string(), + versionName: yup.string() + .matches(versionNameRegExp, + { + message: ErrorMessage.versionName, + excludeEmptyString: true + }) + .required("versionName is a required field"), + productName: yup.string(), + trainModelApi: yup.string(), + batchInferenceId: yup.string(), + deployModelId: yup.string(), + authenticationType: yup.string(), + authenticationKey: yup.string(), + //amlWorkspaceName: yup.mixed().notRequired(), + amlWorkspaceName: yup.mixed() + .when('source', {is: (val) => { return val === 'aml_pipelines'}, + then: yup.string().required('AMLWorkspace is Required'), + otherwise: yup.mixed().notRequired()}), + realTimePredictAPI: yup.string(), + trainModelId: yup.string(), + advancedSettings: yup.string().nullable(true), + selectedVersionName:yup.string(), + versionSourceType:yup.string(), + gitUrl:yup.string(), + gitPersonalAccessToken:yup.string(), + gitVersion:yup.string(), + projectFileUrl:yup.string(), + projectFileContent:yup.string(), + } +); + +export const deleteVersionValidator: ObjectSchema = yup.object().shape( + { + deploymentName: yup.mixed().notRequired(), + deploymentVersionList: yup.array(), + versionName: yup.string(), + productName: yup.string(), trainModelApi: yup.string(), - BatchInferenceAPI: yup.string(), - DeployModelAPI: yup.string(), - AuthenticationType: yup.string(), - AMLWorkspaceId: yup.string(), + batchInferenceId: yup.string(), + deployModelId: yup.string(), + authenticationType: yup.string(), + authenticationKey: yup.string(), + amlWorkspaceName: yup.mixed().notRequired(), + realTimePredictAPI: yup.string(), + trainModelId: yup.string(), + advancedSettings: yup.string().nullable(true), + versionSourceType:yup.string(), + gitUrl:yup.string(), + gitPersonalAccessToken:yup.string(), + gitVersion:yup.string(), + projectFileUrl:yup.string(), + projectFileContent:yup.string(), + selectedVersionName:yup.string() + .test('selectedVersionName', 'Version name does not match', function (value: string) { + const name: string = this.parent.versionName; + if (!value) + return true; + + return value.toLowerCase() === name.toLowerCase(); + }).required("versionName is a required field") } ); @@ -140,7 +195,12 @@ export const deploymentFormValidationSchema: ObjectSchema deployment: deploymentValidator }); -export const versionFormValidationSchema: ObjectSchema = +export const versionFormValidationSchema: ObjectSchema = yup.object().shape({ version: versionFormValidator - }); \ No newline at end of file + }); + + // export const deleteVersionValidationSchema: ObjectSchema = + // yup.object().shape({ + // version: deleteVersionValidator + // }); \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductFormUtils.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductFormUtils.ts index 0f04058..24524f8 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductFormUtils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/ProductFormUtils.ts @@ -1,33 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; import { ObjectSchema } from "yup"; import { IProductModel } from "../../../models"; import { v4 as uuid } from "uuid"; -import {productIdRegExp } from "./RegExp"; +import { productNameRegExp } from "./RegExp"; import { ErrorMessage } from "./ErrorMessage"; -import { IDropdownOption } from "office-ui-fabric-react"; export const shallowCompare = (obj1, obj2) => Object.keys(obj1).length === Object.keys(obj2).length && Object.keys(obj1).every(key => obj2.hasOwnProperty(key) && obj1[key] === obj2[key] ); +/* +export const ProductType: IDropdownOption[] = [ + { key: '', text: "Select" }, + { key: 'RTP', text: "Real-Time Prediction" }, + { key: 'BI', text: "Batch Inference" }, + { key: 'TYOM', text: "Train Your Own Model" }] - export const ProductType: IDropdownOption[] = [ - { key: '', text: "Select" }, - { key: 'realtimeprediction', text: "Real-Time Prediction" }, - { key: 'batchinference', text: "Batch Inference" }, - { key: 'trainyourownmodel', text: "Train Your Own Model" }] - - export const HostType: IDropdownOption[] = [ - { key: '', text: "Select" }, - { key: 'saas', text: "SaaS" }, - { key: 'bringyourowncompute', text: "Bring Your Own Compute" }] - +export const HostType: IDropdownOption[] = [ + { key: '', text: "Select" }, + { key: 'SAAS', text: "SaaS" }, + { key: 'BYOC', text: "Bring Your Own Compute" }] +*/ export const initialProductValues: IProductModel = { hostType: '', owner: '', - productId: '', - productType: '', isDeleted: false, + productName: '', + productType: '', + isDeleted: false, isSaved: false, isModified: false, clientId: uuid() @@ -36,8 +39,10 @@ export const initialProductValues: IProductModel = { export const initialProductList: IProductModel[] = [{ hostType: 'saas', owner: 'v-anirc@microsoft.com', - productId: '1', + productName: '1', productType: 'realtimeprediction', + createdTime: '', + lastUpdatedTime: '', isDeleted: false, isSaved: false, isModified: false, @@ -46,8 +51,10 @@ export const initialProductList: IProductModel[] = [{ { hostType: 'bringyourowncompute', owner: 'zbates@affirma.com', - productId: '2', + productName: '2', productType: 'batchinference', + createdTime: '', + lastUpdatedTime: '', isDeleted: false, isSaved: false, isModified: false, @@ -56,8 +63,10 @@ export const initialProductList: IProductModel[] = [{ { hostType: 'saas', owner: 'zbates@affirma.com', - productId: '3', + productName: '3', productType: 'trainyourownmodel', + createdTime: '', + lastUpdatedTime: '', isDeleted: false, isSaved: false, isModified: false, @@ -75,10 +84,10 @@ export const initialInfoFormValues: IProductInfoFormValues = { const productValidator: ObjectSchema = yup.object().shape( { clientId: yup.string(), - productId: yup.string() - .matches(productIdRegExp, + productName: yup.string() + .matches(productNameRegExp, { - message: ErrorMessage.productID, + message: ErrorMessage.productName, excludeEmptyString: true }).required("Id is a required field"), @@ -86,6 +95,8 @@ const productValidator: ObjectSchema = yup.object().shape( productType: yup.string() .required("Product Type is a required field"), hostType: yup.string().required("Host Type is a required field"), + createdTime: yup.string(), + lastUpdatedTime: yup.string() } ); @@ -94,26 +105,27 @@ export const productInfoValidationSchema: ObjectSchema = product: productValidator }); - export const deleteProductValidator: ObjectSchema = yup.object().shape( - { - clientId: yup.string(), - productId: yup.string(), - selectedProductId: yup.string() - .test('selectedProductid', 'Product id does not match', function (value: string) { - - const productId: string = this.parent.productId; - if (!value) - return true; - - return value.toLowerCase() === productId.toLowerCase(); - }).matches(productIdRegExp, - { - message: ErrorMessage.productID, - excludeEmptyString: true - }).required("Product id is a required field"), - - owner: yup.string(), - hostType:yup.string(), - productType:yup.string() - } - ); \ No newline at end of file +export const deleteProductValidator: ObjectSchema = yup.object().shape( + { + clientId: yup.string(), + productName: yup.string(), + selectedProductId: yup.string() + .test('selectedProductid', 'Product name does not match', function (value: string) { + const productName: string = this.parent.productName; + if (!value) + return true; + + return value.toLowerCase() === productName.toLowerCase(); + }).matches(productNameRegExp, + { + message: ErrorMessage.productName, + excludeEmptyString: true + }).required("Product id is a required field"), + + owner: yup.string(), + hostType: yup.string(), + productType: yup.string(), + createdTime: yup.string(), + lastUpdatedTime: yup.string() + } +); \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/RegExp.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/RegExp.tsx index f212ff8..2ca1423 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/RegExp.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/RegExp.tsx @@ -3,10 +3,10 @@ export const httpURLRegExp = /^((?:https?\:\/\/)(?:[-a-z0-9]+\.)*[-a-z0-9]+.*)$ export const iPAddressRegExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\/([0-9]|1[0-9]|2[0-9]|3[0-2])$/; export const emailRegExp = /((([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))[;]*)+/; export const aplicationID_AADTenantRegExp = /^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/; -export const productIdRegExp = /^[a-z_-][a-z0-9_-]*$/; -export const deploymentIdRegExp = /^[a-z_-][a-z0-9_-]*$/; -export const versionIdRegExp = /^[a-z_-][a-z0-9_-]*$/; -export const workSpaceIdRegExp = /^[a-z_-][a-z0-9_-]*$/; +export const productNameRegExp = /^[a-z_-][a-z0-9_-]*$/; +export const deploymentNameRegExp = /^[a-z_-][a-z0-9_-]*$/; +export const versionNameRegExp = /^[a-z0-9_\.-]*$/; +export const workSpaceNameRegExp = /^[a-z_-][a-z0-9_-]*$/; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/registerYupMethods.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/registerYupMethods.ts index f8dfd04..019a2fd 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/registerYupMethods.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Products/formUtils/registerYupMethods.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; export const registerYupMethods = () => { diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptions.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptions.tsx index 9b2b300..f00501d 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptions.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptions.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Stack, MessageBar, - MessageBarType, + MessageBarType, Dialog, DialogType, FontIcon, DetailsList, DetailsListLayoutMode, SelectionMode, IColumn, @@ -12,23 +12,26 @@ import { } from 'office-ui-fabric-react'; import { useHistory } from "react-router"; import { Loading } from "../../shared/components/Loading"; -import { ISubscriptionsModel, ISubscriptionsWarnings } from '../../models/ISubscriptionsModel'; +import { ISubscriptionsModel, ISubscriptionsWarnings, ISubscriptionsV2Model } from '../../models/ISubscriptionsModel'; import SubscriptionsService from '../../services/SubscriptionsService'; import { getInitialSubscriptionsWarningsModel } from './formUtils/subscriptionFormUtils'; -import {NavLink} from "react-router-dom"; +import { NavLink } from "react-router-dom"; +//import { Formik } from 'formik'; +// import AlternateButton from '../../shared/components/AlternateButton'; +// import FormLabel from '../../shared/components/FormLabel'; interface IDetailsListDocumentsExampleState { columns: IColumn[]; items: ISubscriptionsModel[]; + itemsV2: ISubscriptionsV2Model[]; } const Subscriptions: React.FunctionComponent = () => { const history = useHistory(); - const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() == 'true' ? true : false); - const v2Enabled = (window.Configs.ENABLE_V2.toLowerCase() == 'true' ? true : false); + const v1Enabled = (window.Configs.ENABLE_V1.toLowerCase() === 'true' ? true : false); - const [subscription, setsubscription] = useState([]); + const [subscription, setsubscription] = useState([]); const [state, setstate] = useState(); const [subscriptionWarnings, setsubscriptionWarnings] = useState(getInitialSubscriptionsWarningsModel); const [loadingSubscription, setLoadingSubscription] = useState(true); @@ -37,10 +40,11 @@ const Subscriptions: React.FunctionComponent = () => { const [loadingWarnings, setLoadingWarnings] = useState(true); const [warningDetail, setwarningDetail] = useState(''); const [warningDialogVisible, setwarningDialogVisible] = useState(false); + const [subscriptionV2, setsubscriptionV2] = useState([]); const _onColumnClick = (ev: React.MouseEvent, column: IColumn): void => { if (column.key !== 'operation') { - const { columns, items } = state as IDetailsListDocumentsExampleState; + const { columns, items, itemsV2 } = state as IDetailsListDocumentsExampleState; const newColumns: IColumn[] = columns.slice(); const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0]; newColumns.forEach((newCol: IColumn) => { @@ -52,8 +56,10 @@ const Subscriptions: React.FunctionComponent = () => { newCol.isSortedDescending = true; } }); - const newItems = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending); - setstate({ items: newItems, columns: newColumns }); + const newItemsv1 = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending) + const newItemsv2 = _copyAndSort(itemsV2, currColumn.fieldName!, currColumn.isSortedDescending); + v1Enabled ? setstate({ itemsV2: [], items: newItemsv1, columns: newColumns }) + : setstate({ itemsV2: newItemsv2, items: [], columns: newColumns }); } }; @@ -200,7 +206,7 @@ const Subscriptions: React.FunctionComponent = () => { onRender: (item: ISubscriptionsModel) => { return {item.status}; } - }, + }, { key: 'operation', name: 'Operation', @@ -227,14 +233,133 @@ const Subscriptions: React.FunctionComponent = () => { root: {} }} > - { editdetails(item.offerName,item.subscriptionId) }} /> + { editdetails(item.offerName, item.subscriptionId) }} /> ) } }, ]; - const getStatusList = async (statusarray: string[]) => { + const columnsV2: IColumn[] = [ + { + key: 'subscriptionName', + name: 'Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'subscriptionName', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + // return { editdetailsV2(item.productName, item.subscriptionId) }}>{item.name}; + return {item.subscriptionName}; + } + }, + { + key: 'subscribeid', + name: 'Subscription ID', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'subscriptionId', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.subscriptionId}; + } + }, + { + key: 'productName', + name: 'Product Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'productName', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.productName}; + } + }, + { + key: 'deploymentName', + name: 'Deployment Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'deploymentNamed', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.deploymentName}; + } + }, + { + key: 'status', + name: 'Status', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'status', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.status}; + } + } + ]; + + const getStatusList = async (statusarray: string[]) => { let statusDropDown: IDropdownOption[] = []; statusDropDown.push( { key: 'all', text: 'All' }, @@ -250,7 +375,10 @@ const Subscriptions: React.FunctionComponent = () => { const getSubscriptions = async () => { setLoadingSubscription(true); - const results = await SubscriptionsService.list(); + + let results: any; + v1Enabled ? results = await SubscriptionsService.list() : results = await SubscriptionsService.listV2(); + if (results && !results.hasErrors && results.value) { setLoadStatus(true); @@ -263,12 +391,13 @@ const Subscriptions: React.FunctionComponent = () => { } } getStatusList(stringArray); - setsubscription(results.value); - setstate({ items: results.value, columns: columns }); + v1Enabled ? setsubscription(results.value) : setsubscriptionV2(results.value); + v1Enabled ? setstate({ items: results.value, columns: columns, itemsV2: [] }) : setstate({ items: [], itemsV2: results.value, columns: columnsV2 }); } else { setsubscription([]); - setstate({ items: [], columns: columns }); + setsubscriptionV2([]); + v1Enabled ? setstate({ itemsV2: [], items: [], columns: columns }) : setstate({ items: [], itemsV2: [], columns: columnsV2 }); if (results.hasErrors) { // TODO: display errors alert(results.errors.join(', ')); @@ -283,13 +412,13 @@ const Subscriptions: React.FunctionComponent = () => { setLoadingWarnings(true); const results = await SubscriptionsService.getAllSubscriptionWarnings(); if (results && results.value) { - setsubscriptionWarnings([...results.value]); + setsubscriptionWarnings([...results.value]); } else { setsubscriptionWarnings([]); } setLoadingWarnings(false); - } + } useEffect(() => { getSubscriptions(); @@ -297,8 +426,8 @@ const Subscriptions: React.FunctionComponent = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const editdetails = (offerName:string,subscriptionId: string): void => { - history.push('SubscriptionDetail/'+offerName+'/' + subscriptionId); + const editdetails = (offerName: string, subscriptionId: string): void => { + history.push('SubscriptionDetail/' + offerName + '/' + subscriptionId); }; const _getKey = (item: any, index?: number) => { @@ -306,19 +435,32 @@ const Subscriptions: React.FunctionComponent = () => { } const _onItemInvoked = (item: any) => { - alert(`Item invoked: ${item.name}`); + //alert(`Item invoked: ${item.name}`); } const _onChangeText = (ev: React.FormEvent, text?: string): void => { - - let data = subscription; - let filterdata = text ? data.filter(i => - i.offerName.toLowerCase().indexOf(text.toLowerCase()) > -1 || - i.planName.toLowerCase().indexOf(text.toLowerCase()) > -1 || - i.quantity.toString().toLowerCase().indexOf(text.toLowerCase()) > -1 || - i.subscriptionId.toLowerCase().indexOf(text.toLowerCase()) > -1 || - i.name.toLowerCase().indexOf(text.toLowerCase()) > -1) : data - setstate({ items: filterdata, columns: columns }); + if (v1Enabled) { + let data = subscription; + let filterdata = text ? data.filter(i => + (i.offerName && i.offerName.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.planName && i.planName.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.quantity && i.quantity.toString().toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.subscriptionId && i.subscriptionId.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.name && i.name.toLowerCase().indexOf(text.toLowerCase()) > -1)) : data + setstate({ itemsV2: [], items: filterdata, columns: columns }); + } + else { + let data = subscriptionV2; + let filterdata = text ? data.filter(i => + (i.productName && i.productName.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.baseUrl && i.baseUrl.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.deploymentName && i.deploymentName.toString().toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.subscriptionId && i.subscriptionId.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.primaryKey && i.primaryKey.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.secondaryKey && i.secondaryKey.toLowerCase().indexOf(text.toLowerCase()) > -1) || + (i.subscriptionName && i.subscriptionName.toLowerCase().indexOf(text.toLowerCase()) > -1)) : data + setstate({ items: [], itemsV2: filterdata, columns: columnsV2 }); + } }; const selectOnChange = (event: React.FormEvent, option?: IDropdownOption, index?: number) => { @@ -326,14 +468,21 @@ const Subscriptions: React.FunctionComponent = () => { let text = (option.key as string); if (text !== 'all') { - let data = subscription; + let data: any = []; + if (v1Enabled) { + data = subscription; + } + else { + data = subscriptionV2; + } let filterdata = text ? data.filter(i => i.status.toLowerCase() === text.toLowerCase()) : data - setstate({ items: filterdata, columns: columns }); + v1Enabled ? setstate({ itemsV2: [], items: filterdata, columns: columns }) + : setstate({ itemsV2: filterdata, items: [], columns: columnsV2 }); } else { - setstate({ items: subscription, columns: columns }); + v1Enabled ? setstate({ itemsV2: [], items: subscription, columns: columns }) + : setstate({ itemsV2: subscriptionV2, items: [], columns: columnsV2 }); } - } }; @@ -352,6 +501,10 @@ const Subscriptions: React.FunctionComponent = () => { setwarningDialogVisible(false); }; + // const hideSubscriptionv2Dialog = (): void => { + // setSubscriptionv2DialogVisible(false); + // }; + const SubscriptionWarnings = (): React.ReactElement | null => { if (loadingWarnings) { return ( @@ -383,8 +536,8 @@ const Subscriptions: React.FunctionComponent = () => { handleDeleteWarning(idx) }}> Subscription Id:{value.subscriptionId} - - Click {e.preventDefault();showWarningDialog(value.details)}}>here for more details. - + Click { e.preventDefault(); showWarningDialog(value.details) }}>here for more details. + ) })} {
- { _onColumnClick(event as React.MouseEvent, column as IColumn) }} - /> + { + v1Enabled ? + //Subscriptionv1 + { _onColumnClick(event as React.MouseEvent, column as IColumn) }} + /> + : + //Subscriptionv2 + { _onColumnClick(event as React.MouseEvent, columnsV2 as IColumn) }} + /> + }
} + {/* */} ); } diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptionv1.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptionv1.tsx new file mode 100644 index 0000000..938ae67 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptionv1.tsx @@ -0,0 +1,361 @@ +import React, { useEffect, useState } from 'react'; +import { + Stack, + MessageBar, + MessageBarType, + Dialog, DialogType, + FontIcon, + DetailsList, DetailsListLayoutMode, SelectionMode, IColumn, + TextField, + IDropdownOption, + Dropdown, +} from 'office-ui-fabric-react'; +import { useHistory } from "react-router"; +import { Loading } from "../../shared/components/Loading"; +import { ISubscriptionsModel, ISubscriptionsWarnings } from '../../models/ISubscriptionsModel'; +import SubscriptionsService from '../../services/SubscriptionsService'; +import { getInitialSubscriptionsWarningsModel } from './formUtils/subscriptionFormUtils'; +import { NavLink } from "react-router-dom"; + +interface IDetailsListDocumentsExampleState { + columns: IColumn[]; + items: ISubscriptionsModel[]; +} + +const Subscriptionsv1: React.FunctionComponent = () => { + const history = useHistory(); + + const [subscription, setsubscription] = useState([]); + const [state, setstate] = useState(); + const [loadingSubscription, setLoadingSubscription] = useState(true); + const [loadStatus, setLoadStatus] = useState(true); + const [statusList, setStatusList] = useState([]); + + const _onColumnClick = (ev: React.MouseEvent, column: IColumn): void => { + if (column.key !== 'operation') { + const { columns, items } = state as IDetailsListDocumentsExampleState; + const newColumns: IColumn[] = columns.slice(); + const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0]; + newColumns.forEach((newCol: IColumn) => { + if (newCol === currColumn) { + currColumn.isSortedDescending = !currColumn.isSortedDescending; + currColumn.isSorted = true; + } else { + newCol.isSorted = false; + newCol.isSortedDescending = true; + } + }); + const newItems = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending); + setstate({ items: newItems, columns: newColumns }); + } + }; + + function _copyAndSort(items: T[], columnKey: string, isSortedDescending?: boolean): T[] { + const key = columnKey as keyof T; + return items.slice(0).sort((a: T, b: T) => ((isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1)); + } + + const columns: IColumn[] = [ + { + key: 'subscribeid', + name: 'Subscription ID', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'subscriptionId', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsModel) => { + return {item.subscriptionId}; + } + }, + { + key: 'name', + name: 'Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'name', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsModel) => { + return {item.name}; + } + }, + { + key: 'offerid', + name: 'Offer Id', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'offerId', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsModel) => { + return {item.offerName}; + } + }, + { + key: 'planid', + name: 'Plan Id', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'planId', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsModel) => { + return {item.planName}; + } + }, + { + key: 'quantity', + name: 'Quantity', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'quantity', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsModel) => { + return {item.quantity}; + } + }, + { + key: 'status', + name: 'Status', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'status', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsModel) => { + return {item.status}; + } + }, + { + key: 'operation', + name: 'Operation', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + isRowHeader: true, + isResizable: true, + isSorted: false, + fieldName: '', + minWidth: 210, + maxWidth: 350, + onRender: (item: ISubscriptionsModel) => { + return ( + + { editdetails(item.offerName, item.subscriptionId) }} /> + + ) + } + }, + ]; + + const getStatusList = async (statusarray: string[]) => { + let statusDropDown: IDropdownOption[] = []; + statusDropDown.push( + { key: 'all', text: 'All' }, + ) + statusarray.map((value, index) => { + statusDropDown.push( + { key: value.toLowerCase(), text: value }, + ) + return statusDropDown; + }) + setStatusList(statusDropDown); + } + + const getSubscriptions = async () => { + setLoadingSubscription(true); + const results = await SubscriptionsService.list(); + if (results && !results.hasErrors && results.value) { + + setLoadStatus(true); + const map = new Map(); + let stringArray: string[] = []; + for (const item of results.value.map(s => s.status)) { + if (!map.has(item)) { + map.set(item, true); // set any value to Map + stringArray.push(item); + } + } + getStatusList(stringArray); + setsubscription(results.value); + setstate({ items: results.value, columns: columns }); + } + else { + setsubscription([]); + setstate({ items: [], columns: columns }); + if (results.hasErrors) { + // TODO: display errors + alert(results.errors.join(', ')); + } + } + setLoadingSubscription(false); + setLoadStatus(false); + } + + useEffect(() => { + getSubscriptions(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const editdetails = (offerName: string, subscriptionId: string): void => { + history.push('SubscriptionDetail/' + offerName + '/' + subscriptionId); + }; + + const _getKey = (item: any, index?: number) => { + return item.key; + } + + const _onItemInvoked = (item: any) => { + //alert(`Item invoked: ${item.name}`); + } + + const _onChangeText = (ev: React.FormEvent, text?: string): void => { + + let data = subscription; + let filterdata = text ? data.filter(i => + i.offerName.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.planName.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.quantity.toString().toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.subscriptionId.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.name.toLowerCase().indexOf(text.toLowerCase()) > -1) : data + setstate({ items: filterdata, columns: columns }); + }; + + const selectOnChange = (event: React.FormEvent, option?: IDropdownOption, index?: number) => { + if (option) { + let text = (option.key as string); + + if (text !== 'all') { + let data = subscription; + let filterdata = text ? data.filter(i => i.status.toLowerCase() === text.toLowerCase()) : data + setstate({ items: filterdata, columns: columns }); + } + else { + setstate({ items: subscription, columns: columns }); + } + + } + }; + + return ( + + { + loadingSubscription ? + + : + + + + + + + + +
+ + + {loadStatus ? : + { + selectOnChange(event, option); + }} defaultSelectedKey={'all'} className="statusdrp" />} +
+
+ { _onColumnClick(event as React.MouseEvent, column as IColumn) }} + /> +
+
+ } +
+ ) +} + +export default Subscriptionsv1; \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptionv2.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptionv2.tsx new file mode 100644 index 0000000..d8b65e9 --- /dev/null +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/Subscriptionv2.tsx @@ -0,0 +1,410 @@ +import React, { useEffect, useState } from 'react'; +import { + Stack, + MessageBar, + MessageBarType, + Dialog, DialogType, + FontIcon, + DetailsList, DetailsListLayoutMode, SelectionMode, IColumn, + TextField, + IDropdownOption, + Dropdown, +} from 'office-ui-fabric-react'; +import { useHistory } from "react-router"; +import { Loading } from "../../shared/components/Loading"; +import { ISubscriptionsV2Model, ISubscriptionsWarnings } from '../../models/ISubscriptionsModel'; +import SubscriptionsService from '../../services/SubscriptionsService'; +import { getInitialSubscriptionsWarningsModel } from './formUtils/subscriptionFormUtils'; +import { NavLink } from "react-router-dom"; + + +interface IDetailsListDocumentsExampleState { + columns: IColumn[]; + items: ISubscriptionsV2Model[]; +} +const SubscriptionsV2: React.FunctionComponent = () => { + const history = useHistory(); + + const [subscriptionV2, setsubscriptionV2] = useState([]); + const [state, setstate] = useState(); + const [loadingSubscriptionV2, setLoadingSubscriptionV2] = useState(true); + const [loadStatus, setLoadStatus] = useState(true); + const [statusList, setStatusList] = useState([]); + + + const _onColumnClick = (ev: React.MouseEvent, column: IColumn): void => { + if (column.key !== 'operation') { + const { columns, items } = state as IDetailsListDocumentsExampleState; + const newColumns: IColumn[] = columns.slice(); + const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0]; + newColumns.forEach((newCol: IColumn) => { + if (newCol === currColumn) { + currColumn.isSortedDescending = !currColumn.isSortedDescending; + currColumn.isSorted = true; + } else { + newCol.isSorted = false; + newCol.isSortedDescending = true; + } + }); + const newItems = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending); + setstate({ items: newItems, columns: newColumns }); + } + }; + + function _copyAndSort(items: T[], columnKey: string, isSortedDescending?: boolean): T[] { + const key = columnKey as keyof T; + return items.slice(0).sort((a: T, b: T) => ((isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1)); + } + + const columns: IColumn[] = [ + { + key: 'subscribeid', + name: 'Subscription ID', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'subscriptionId', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.subscriptionId}; + } + }, + { + key: 'subscriptionName', + name: 'Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'subscriptionName', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.subscriptionName}; + } + }, + { + key: 'productName', + name: 'Product Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'productName', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.productName}; + } + }, + { + key: 'deploymentName', + name: 'Deployment Name', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'deploymentNamed', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.deploymentName}; + } + }, + { + key: 'status', + name: 'Status', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'status', + minWidth: 100, + maxWidth: 100, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.status}; + } + }, + { + key: 'baseUrl', + name: 'Base Url', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'baseUrl', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.baseUrl}; + } + }, + { + key: 'primaryKey', + name: 'Primary Key', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'primaryKey', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.primaryKey}; + } + }, + { + key: 'secondaryKey', + name: 'Secondary Key', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + fieldName: 'secondaryKey', + minWidth: 210, + maxWidth: 350, + data: 'string', + isRowHeader: true, + isResizable: true, + isSorted: true, + isSortedDescending: false, + sortAscendingAriaLabel: 'Sorted A to Z', + sortDescendingAriaLabel: 'Sorted Z to A', + isPadded: true, + onRender: (item: ISubscriptionsV2Model) => { + return {item.secondaryKey}; + } + }, + { + key: 'operation', + name: 'Operation', + className: '', + iconClassName: '', + ariaLabel: '', + iconName: '', + isIconOnly: false, + isRowHeader: true, + isResizable: true, + isSorted: false, + fieldName: '', + minWidth: 210, + maxWidth: 350, + onRender: (item: ISubscriptionsV2Model) => { + return ( + + { editdetails(item.productName, item.subscriptionId) }} /> + + ) + } + }, + ]; + + const getStatusList = async (statusarray: string[]) => { + let statusDropDown: IDropdownOption[] = []; + statusDropDown.push( + { key: 'all', text: 'All' }, + ) + statusarray.map((value, index) => { + statusDropDown.push( + { key: value.toLowerCase(), text: value }, + ) + return statusDropDown; + }) + setStatusList(statusDropDown); + } + + const getSubscriptions = async () => { + setLoadingSubscriptionV2(true); + const results = await SubscriptionsService.listV2(); + if (results && !results.hasErrors && results.value) { + + setLoadStatus(true); + const map = new Map(); + let stringArray: string[] = []; + for (const item of results.value.map(s => s.status)) { + if (!map.has(item)) { + map.set(item, true); // set any value to Map + stringArray.push(item); + } + } + getStatusList(stringArray); + setsubscriptionV2(results.value); + setstate({ items: results.value, columns: columns }); + } + else { + setsubscriptionV2([]); + setstate({ items: [], columns: columns }); + if (results.hasErrors) { + // TODO: display errors + alert(results.errors.join(', ')); + } + } + setLoadingSubscriptionV2(false); + setLoadStatus(false); + } + + useEffect(() => { + getSubscriptions(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const editdetails = (offerName: string, subscriptionId: string): void => { + history.push('SubscriptionDetail/' + offerName + '/' + subscriptionId); + }; + + const _getKey = (item: any, index?: number) => { + return item.key; + } + + const _onItemInvoked = (item: any) => { + //alert(`Item invoked: ${item.name}`); + } + + const _onChangeText = (ev: React.FormEvent, text?: string): void => { + + let data = subscriptionV2; + let filterdata = text ? data.filter(i => + i.productName.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.baseUrl.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.deploymentName.toString().toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.subscriptionId.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.primaryKey.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.secondaryKey.toLowerCase().indexOf(text.toLowerCase()) > -1 || + i.subscriptionName.toLowerCase().indexOf(text.toLowerCase()) > -1) : data + setstate({ items: filterdata, columns: columns }); + }; + + const selectOnChange = (event: React.FormEvent, option?: IDropdownOption, index?: number) => { + if (option) { + let text = (option.key as string); + + if (text !== 'all') { + let data = subscriptionV2; + let filterdata = text ? data.filter(i => i.status.toLowerCase() === text.toLowerCase()) : data + setstate({ items: filterdata, columns: columns }); + } + else { + setstate({ items: subscriptionV2, columns: columns }); + } + + } + }; + + return ( + + { + loadingSubscriptionV2 ? + + : + + + + + + + + +
+ + + {loadStatus ? : + { + selectOnChange(event, option); + }} defaultSelectedKey={'all'} className="statusdrp" />} +
+
+ { _onColumnClick(event as React.MouseEvent, column as IColumn) }} + /> +
+
+ } +
+ ) +} + +export default SubscriptionsV2; \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts index 65e3457..ced6535 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/Subscriptions/formUtils/subscriptionFormUtils.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as yup from "yup"; import { ObjectSchema } from "yup"; import { ISubscriptionsPostModel, ISubscriptionsModel, ISubscriptionsWarnings } from "../../../models"; @@ -9,7 +12,7 @@ export const getInitialSubscriptionsPostModel = (): ISubscriptionsPostModel => { name: '', offerName: '', planName: '', - availablePlanName:'', + availablePlanName: '', owner: '', quantity: 1, beneficiaryTenantId: '', @@ -22,7 +25,7 @@ export const getInitialSubscriptionsPostModel = (): ISubscriptionsPostModel => { monthlyBase: 0, privatePlan: false, inputParameters: [], - planlist:[] + planlist: [] } }; @@ -67,6 +70,131 @@ export const getInitialSubscriptionsModel = (): ISubscriptionsModel => { } }; +export const getInitialSubscriptionsV1List = +{ + hasErrors: false, + value: [ + { + subscriptionId: '1', + name: '1', + offerName: '1', + planName: '1', + owner: '1', + quantity: 0, + beneficiaryTenantId: '1', + purchaserTenantId: '2', + subscribeWebhookName: '2', + unsubscribeWebhookName: '2', + suspendWebhookName: '2', + deleteDataWebhookName: '2', + priceModel: '2', + monthlyBase: 0, + privatePlan: false, + inputParameters: [], + provisioningStatus: '2', + + publisherId: '2', + status: 'Test', + isTest: false, + allowedCustomerOperationsMask: 0, + sessionMode: '2', + sandboxType: '2', + isFreeTrial: false, + createdTime: '', + activatedTime: '', + lastUpdatedTime: '', + lastSuspendedTime: '', + unsubscribedTime: '', + dataDeletedTime: '', + operationId: '', + deploymentName: '1', + deploymentId: '2', + resourceGroup: '1', + activatedBy: '1', + }, + { + subscriptionId: '2', + name: '1', + offerName: '1', + planName: '1', + owner: '1', + quantity: 0, + beneficiaryTenantId: '1', + purchaserTenantId: '2', + subscribeWebhookName: '2', + unsubscribeWebhookName: '2', + suspendWebhookName: '2', + deleteDataWebhookName: '2', + priceModel: '2', + monthlyBase: 0, + privatePlan: false, + inputParameters: [], + provisioningStatus: '2', + + publisherId: '2', + status: 'Test1', + isTest: false, + allowedCustomerOperationsMask: 0, + sessionMode: '2', + sandboxType: '2', + isFreeTrial: false, + createdTime: '', + activatedTime: '', + lastUpdatedTime: '', + lastSuspendedTime: '', + unsubscribedTime: '', + dataDeletedTime: '', + operationId: '', + deploymentName: '1', + deploymentId: '2', + resourceGroup: '1', + activatedBy: '1', + } + ] +}; + +export const getInitialSubscriptionsV2 = +{ + subscriptionId: '', + subscriptionName: '', + userId: '', + productName: '', + deploymentName: '', + status: '', + baseUrl: '', + primaryKey: '', + secondaryKey: '' +}; + +export const getInitialSubscriptionsV2List = +{ + hasErrors: false, + value: [ + { + subscriptionId: '1', + subscriptionName: '1', + userId: '1', + productName: 'EDDI', + deploymentName: 'westUs', + status: 'Subscribed', + baseUrl: 'https://luna.apim.net/eddi/eastus/predict', + primaryKey: '*************', + secondaryKey: '*************' + }, + { + subscriptionId: '60bc9d4a-09d7-42e1-89aa-c188e0aff9db', + subscriptionName: 'scottgutest', + userId: 'scottgu@microsoft.com', + productName: 'BDDI', + deploymentName: 'eastus', + status: 'UnSubscribed', + baseUrl: 'https://luna.apim.net/bddi/eastus/predict', + primaryKey: '*************', + secondaryKey: '*************' + } + ] +}; + export const getInitialSubscriptionsWarningsModel = (): ISubscriptionsWarnings[] => { return ( [ @@ -88,8 +216,8 @@ export const subscriptionValidator: ObjectSchema = yup. { clientId: yup.string(), subscriptionId: yup.string(), - subscriptionName: yup.string().required("Subscription Name is required"), - availablePlanName:yup.string().required('Please select plan'), + subscriptionName: yup.string().required("Subscription Name is required"), + availablePlanName: yup.string().required('Please select plan'), name: yup.string(), offerName: yup.string(), planName: yup.string(), @@ -105,7 +233,7 @@ export const subscriptionValidator: ObjectSchema = yup. monthlyBase: yup.number(), privatePlan: yup.boolean(), inputParameters: yup.array(), - planlist:yup.array() + planlist: yup.array() } ); @@ -113,21 +241,21 @@ export const UpdateplanValidator: ObjectSchema = yup.ob { clientId: yup.string(), subscriptionId: yup.string(), - subscriptionName: yup.string(), - availablePlanName:yup.string().required('Available plan is required') - .when('planName', { - is: (val) => { - return val !== "" - }, - then: yup.string().test('existingPlan', 'That plan is already set', function (value: string) { - const currentPlanName: string = this.parent.planName; - if (!value) - return true; + subscriptionName: yup.string(), + availablePlanName: yup.string().required('Available plan is required') + .when('planName', { + is: (val) => { + return val !== "" + }, + then: yup.string().test('existingPlan', 'That plan is already set', function (value: string) { + const currentPlanName: string = this.parent.planName; + if (!value) + return true; - return !value.toLowerCase().includes(currentPlanName.toLowerCase()); - }).required('Available plan is required'), - otherwise: yup.mixed().notRequired() - }), + return !value.toLowerCase().includes(currentPlanName.toLowerCase()); + }).required('Available plan is required'), + otherwise: yup.mixed().notRequired() + }), name: yup.string(), offerName: yup.string(), planName: yup.string(), @@ -143,6 +271,6 @@ export const UpdateplanValidator: ObjectSchema = yup.ob monthlyBase: yup.number(), privatePlan: yup.boolean(), inputParameters: yup.array(), - planlist:yup.array() + planlist: yup.array() } ); \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/index.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/index.tsx index 6a0e7f7..0f529a7 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/index.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/routes/index.tsx @@ -59,10 +59,6 @@ export const Products = loadable(() => import('./Products/Products'), { LoadingComponent: Loading }); -export const ModifyProductInfo = loadable(() => import('./Products/Info'), { - LoadingComponent: Loading -}); - export const ProductDetail = loadable(() => import('./Products/ProductDetail'), { LoadingComponent: Loading }); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/serviceWorker.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/serviceWorker.ts index 75b823c..458cb8e 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/serviceWorker.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/serviceWorker.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + // This optional code is used to register a service worker. // register() is not called by default. diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplateParameterService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplateParameterService.ts index d5c1844..3d2f874 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplateParameterService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplateParameterService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {IARMTemplateParameterModel, Result} from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplatesService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplatesService.ts index d8f85b0..4c8964d 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplatesService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ArmTemplatesService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {IARMTemplateModel, Result} from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/IpConfigService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/IpConfigService.ts index 19ea334..a9a5289 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/IpConfigService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/IpConfigService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { IIpConfigModel, Result } from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/MetersService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/MetersService.ts index 28e4d4b..1ac6c5c 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/MetersService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/MetersService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {ICustomMeterModel, Result} from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferParameterService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferParameterService.ts index 464e352..7d698b4 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferParameterService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferParameterService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { Result } from "../models"; import { IOfferParameterModel } from "../models/IOfferParameterModel"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferService.ts index eebb504..f5130d0 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/OfferService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {IOfferModel, Result} from "../models"; import {IOfferWarningsModel} from "../models/IOfferWarningsModel"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/PlansService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/PlansService.ts index 5d0484a..de882ef 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/PlansService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/PlansService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import {IPlanModel, Result, IRestrictedUsersModel, ICustomMeterDimensionsModel} from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ProductService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ProductService.ts index 0d47b44..4da1c50 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ProductService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ProductService.ts @@ -1,9 +1,41 @@ -import { ServiceBase } from "./ServiceBase"; -import { Result, IProductModel, IDeploymentsModel, IDeploymentVersionModel, IAMLWorkSpaceModel } from "../models"; -import { v4 as uuid } from "uuid"; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import {ServiceBase} from "./ServiceBase"; +import { + IAMLWorkSpaceModel, + IDeploymentsModel, + IDeploymentVersionModel, + ILookupType, + IProductModel, + Result, + ISourceModel, + IPipeLineModel +} from "../models"; +import {v4 as uuid} from "uuid"; export default class ProductService extends ServiceBase { + public static async getProductTypes(): Promise> { + + var result = await this.requestJson({ + url: `/productTypes`, + method: "GET" + }); + + return result; + } + + public static async getHostTypes(): Promise> { + + var result = await this.requestJson({ + url: `/hostTypes`, + method: "GET" + }); + + return result; + } + public static async list(): Promise> { var result = await this.requestJson({ @@ -17,10 +49,10 @@ export default class ProductService extends ServiceBase { return result; } - public static async get(productId: string): Promise> { + public static async get(productName: string): Promise> { var result = await this.requestJson({ - url: `/products/${productId}`, + url: `/products/${productName}`, method: "GET" }); @@ -32,7 +64,7 @@ export default class ProductService extends ServiceBase { public static async update(model: IProductModel): Promise> { var result = await this.requestJson({ - url: `/products/${model.productId}`, + url: `/products/${model.productName}`, method: "PUT", data: model }); @@ -43,9 +75,9 @@ export default class ProductService extends ServiceBase { return result; } - public static async delete(productId: string): Promise> { + public static async delete(productName: string): Promise> { var result = await this.requestJson>({ - url: `/products/${productId}`, + url: `/products/${productName}`, method: "DELETE" }); return result; @@ -53,7 +85,7 @@ export default class ProductService extends ServiceBase { public static async create(model: IProductModel): Promise> { var result = await this.requestJson({ - url: `/products/${model.productId}`, + url: `/products/${model.productName}`, method: "PUT", data: model }); @@ -67,10 +99,10 @@ export default class ProductService extends ServiceBase { //#region Deployments - public static async getDeploymentListByProductId(productId: string): Promise> { + public static async getDeploymentListByProductName(productName: string): Promise> { var result = await this.requestJson({ - url: `/products/${productId}/deployments`, + url: `/products/${productName}/deployments`, method: "GET" }); @@ -80,10 +112,10 @@ export default class ProductService extends ServiceBase { return result; } - public static async getDeploymentByProductId(productId: string,deploymentId:string): Promise> { + public static async getDeploymentByProductName(productName: string,deploymentName:string): Promise> { var result = await this.requestJson({ - url: `/products/${productId}/deployments/${deploymentId}`, + url: `/products/${productName}/deployments/${deploymentName}`, method: "GET" }); @@ -92,7 +124,7 @@ export default class ProductService extends ServiceBase { public static async createOrUpdateDeployment(model: IDeploymentsModel): Promise> { var result = await this.requestJson({ - url: `/products/${model.productId}/deployments/${model.deploymentId}`, + url: `/products/${model.productName}/deployments/${model.deploymentName}`, method: "PUT", data: model }); @@ -103,9 +135,9 @@ export default class ProductService extends ServiceBase { return result; } - public static async deleteDeployment(productId: string,deploymentId:string): Promise> { + public static async deleteDeployment(productName: string,deploymentName:string): Promise> { var result = await this.requestJson>({ - url: `/products/${productId}/deployments/${deploymentId}`, + url: `/products/${productName}/deployments/${deploymentName}`, method: "DELETE" }); return result; @@ -113,20 +145,20 @@ export default class ProductService extends ServiceBase { //#endregion //#region Version - public static async getDeploymentVersionListByDeploymentId(productId: string,deploymentId:string): Promise> { + public static async getDeploymentVersionListByDeploymentName(productName: string,deploymentName:string): Promise> { var result = await this.requestJson({ - url: `/products/${productId}/deployments/${deploymentId}/versions`, + url: `/products/${productName}/deployments/${deploymentName}/apiversions`, method: "GET" }); return result; } - public static async getDeploymentVersionById(productId: string,deploymentId:string,versionId:string): Promise> { + public static async getDeploymentVersionById(productName: string,deploymentName:string,versionName:string): Promise> { var result = await this.requestJson({ - url: `/products/${productId}/deployments/${deploymentId}/versions/${versionId}`, + url: `/products/${productName}/deployments/${deploymentName}/apiversions/${versionName}`, method: "GET" }); @@ -135,7 +167,7 @@ export default class ProductService extends ServiceBase { public static async createOrUpdateDeploymentVersion(model: IDeploymentVersionModel): Promise> { var result = await this.requestJson({ - url: `/products/${model.productID}/deployments/${model.deploymentId}/versions/${model.versionId}`, + url: `/products/${model.productName}/deployments/${model.deploymentName}/apiversions/${model.versionName}`, method: "PUT", data: model }); @@ -143,9 +175,9 @@ export default class ProductService extends ServiceBase { return result; } - public static async deleteDeploymentVersion(productId: string,deploymentId:string,versionId:string): Promise> { + public static async deleteDeploymentVersion(productName: string,deploymentName:string,versionName:string): Promise> { var result = await this.requestJson>({ - url: `/products/${productId}/deployments/${deploymentId}/Versions/${versionId}`, + url: `/products/${productName}/deployments/${deploymentName}/apiversions/${versionName}`, method: "DELETE" }); return result; @@ -167,10 +199,10 @@ export default class ProductService extends ServiceBase { return result; } - public static async getAmlWorkSpaceById(workspaceId:string): Promise> { + public static async getAmlWorkSpaceByName(workspaceName:string): Promise> { var result = await this.requestJson({ - url: `/amlworkspaces/${workspaceId}`, + url: `/amlworkspaces/${workspaceName}`, method: "GET" }); @@ -182,7 +214,7 @@ export default class ProductService extends ServiceBase { public static async createOrUpdateWorkSpace(model: IAMLWorkSpaceModel): Promise> { var result = await this.requestJson({ - url: `/amlworkspaces/${model.workspaceId}`, + url: `/amlworkspaces/${model.workspaceName}`, method: "PUT", data: model }); @@ -193,12 +225,57 @@ export default class ProductService extends ServiceBase { return result; } - public static async deleteWorkSpace(workspaceId: string): Promise> { + public static async deleteWorkSpace(workspaceName: string): Promise> { var result = await this.requestJson>({ - url: `/amlworkspaces/${workspaceId}/`, + url: `/amlworkspaces/${workspaceName}/`, method: "DELETE" }); return result; } + + public static async getPublishedPipeLineByAmlWorkSpaceList(amlWorkspace:string): Promise> { + + var result = await this.requestJson({ + url: `/amlworkspaces/${amlWorkspace}/pipelines/`, + method: "GET" + }); + return result; + } + + public static async getbatchInferenceList(): Promise> { + + var result = await this.requestJson({ + url: `/amlworkspaces/`, + method: "GET" + }); + + if (!result.hasErrors && result.value) + result.value.map(u => u.clientId = uuid()); + + return result; + } + + public static async gettrainModelList(): Promise> { + + var result = await this.requestJson({ + url: `/amlworkspaces/`, + method: "GET" + }); + + if (!result.hasErrors && result.value) + result.value.map(u => u.clientId = uuid()); + + return result; + } + + public static async getSourceModelList(): Promise> { + + var result = await this.requestJson({ + url: `/apiVersions/sourceTypes/`, + method: "GET" + }); + + return result; + } //#endregion } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ServiceBase.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ServiceBase.ts index 2acdf0c..67aad2e 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ServiceBase.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/ServiceBase.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {Result} from "../models"; import * as jsonToUrl from "json-to-url"; import Axios, {AxiosRequestConfig, AxiosResponse} from "axios"; @@ -99,6 +102,10 @@ export abstract class ServiceBase { else result = new Result(null, false, error.response.data.error.details); } + else if (error.response.status === 404) { + ServiceBase.dispatchGlobalError("Method not found!"); + result = new Result(null, false,null); + } else { let message = ''; if (error.response.data.error.code) { @@ -177,6 +184,10 @@ export abstract class ServiceBase { else result = new Result(null, false, error.response.data.error.details); } + else if (error.response.status === 404) { + ServiceBase.dispatchGlobalError("Method not found!"); + result = new Result(null, false,null); + } else { let message = (error.title ? error.title : error.mesage); ServiceBase.dispatchGlobalError(message); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/SubscriptionsService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/SubscriptionsService.ts index da2cc58..4a788fa 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/SubscriptionsService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/SubscriptionsService.ts @@ -1,23 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {ServiceBase} from "../services/ServiceBase"; import { ISubscriptionsModel, ISubscriptionsPostModel, ISubscriptionsV2Model, ISubscriptionsWarnings, - Result + Result, + ISubscriptionsV2RefreshKeyModel } from "../models"; export default class SubscriptionsService extends ServiceBase { - public static async listV2(): Promise> { - - var result = await this.requestJson({ - url: `/subscriptions`, - method: "GET" - }); - - return result; - } + //#region Subscription V1 public static async list(): Promise> { @@ -91,4 +87,65 @@ export default class SubscriptionsService extends ServiceBase { return result; } + //#endregion + + //#region SubscriptionV2 + + public static async listV2(): Promise> { + + var result = await this.requestJson({ + url: `/apisubscriptions`, + method: "GET" + }); + + return result; + } + + public static async getV2(subscriptionId): Promise> { + + var result = await this.requestJson({ + url: `/apisubscriptions/${subscriptionId}`, + method: "GET" + }); + return result; + } + + public static async createV2(model: ISubscriptionsV2Model): Promise> { + var result = await this.requestJson({ + url: `/subscriptions/create`, + method: "POST", + data: model + }); + + return result; + } + public static async updateV2(model: ISubscriptionsV2Model): Promise> { + var result = await this.requestJson({ + url: `/subscriptions/${model.subscriptionId}`, + method: "PUT", + data: model + }); + + return result; + } + + public static async RefreshKey(model: ISubscriptionsV2RefreshKeyModel): Promise> { + var result = await this.requestJson({ + url: `/subscriptions/${model.subscriptionId}/regenerateKey`, + method: "POST", + data: model + }); + + return result; + } + + public static async deleteV2(subscriptionId: string): Promise> { + var result = await this.requestJson>({ + url: `/subscriptions/${subscriptionId}`, + method: "DELETE" + }); + return result; + } + + //#endregion } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/TelemetryDataConnectorService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/TelemetryDataConnectorService.ts index e276062..c715392 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/TelemetryDataConnectorService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/TelemetryDataConnectorService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { ITelemetryDataConnectorModel, Result } from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksParametersService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksParametersService.ts index 0ef79f9..79f6e71 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksParametersService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksParametersService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { IWebHookParameterModel, Result } from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksService.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksService.ts index 3ae4dc2..9f74793 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksService.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/services/WebHooksService.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { ServiceBase } from "../services/ServiceBase"; import { IWebHookModel, Result } from "../models"; import { v4 as uuid } from "uuid"; diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/infomessages.tsx b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/infomessages.tsx index 0a22dab..53df246 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/infomessages.tsx +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/infomessages.tsx @@ -2,39 +2,74 @@ export const Offers = { offer: { - ID:"ID–Create a unique offer ID for each offer in your account. This ID will be visible to customers in the URL of the marketplace offer and the Azure Resource Manager templates (if applicable)" - + "

Note: This ID cannot be modified after creation and must correspond to Offer ID specified in the Partner Center. ", + ID: "ID–Create a unique offer ID for each offer in your account. This ID will be visible to customers in the URL of the marketplace offer and the Azure Resource Manager templates (if applicable)" + + "

Note: This ID cannot be modified after creation and must correspond to Offer ID specified in the Partner Center. ", Alias: "Alias: Enter a descriptive name that we will use to refer to this offer within Luna. Once you select save, you cannot change the offer alias." - + "

Note: This name will not be used in the marketplace and is different than the Offer Name specified in the Partner Center and other values that will be shown to the customers.", - Version:"Version: The version number of your offer. Customers will see this info on the offer's details page." - + "

Note: This must correspond to App version specified on the Properties tab of the Partner Center. ", - Owners:"Owners: Specify emails of offer owners. " - + "

Note: This must correspond to Preview Audience specified on the Preview tab of the Partner Center.", - HostSubscription:"HostSubscription:Specify tenant ID of host subscription. " - + "

Note: This must correspond to the Azure Active Directory tenant ID specified on the Technical configuration tab of the Partner Center.", - Parameters:"Configure additional parameters for user subscription. User will provide these information before the application is being provisioned. The parameter can be used later during the provisioning." + + "

Note: This name will not be used in the marketplace and is different than the Offer Name specified in the Partner Center and other values that will be shown to the customers.", + Version: "Version: The version number of your offer. Customers will see this info on the offer's details page." + + "

Note: This must correspond to App version specified on the Properties tab of the Partner Center. ", + Owners: "Owners: Specify emails of offer owners. " + + "

Note: This must correspond to Preview Audience specified on the Preview tab of the Partner Center.", + HostSubscription: "HostSubscription:Specify tenant ID of host subscription. " + + "

Note: This must correspond to the Azure Active Directory tenant ID specified on the Technical configuration tab of the Partner Center.", + Parameters: "Configure additional parameters for user subscription. User will provide these information before the application is being provisioned. The parameter can be used later during the provisioning." }, - ipAddress:"Pre-allocate ip blocks in your private vNet and configure how many ip addresses needed per subscription. This is needed only when some Azure services are running in your private vNet as a part of the application.", - armTemplates:"ARM templates can be used to manage Azure resources when user create, update or delete the subscription. ARM template parameters are extracted automatically from uploaded ARM templates. ARM template parameter values will be evaluated as C# expression.", - webHooks:"Webhooks can be used to define your own business logic when user create, update or delete the subscription. For example, you can define a webhook to send a welcome email to the user when they create the subscription.", - meters:"Define custom meters and how to collect meter events from telemetry data.", + ipAddress: "Pre-allocate ip blocks in your private vNet and configure how many ip addresses needed per subscription. This is needed only when some Azure services are running in your private vNet as a part of the application.", + armTemplates: "ARM templates can be used to manage Azure resources when user create, update or delete the subscription. ARM template parameters are extracted automatically from uploaded ARM templates. ARM template parameter values will be evaluated as C# expression.", + webHooks: "Webhooks can be used to define your own business logic when user create, update or delete the subscription. For example, you can define a webhook to send a welcome email to the user when they create the subscription.", + meters: "Define custom meters and how to collect meter events from telemetry data.", plans: { - planId:"Plan ID - Create a unique plan ID for each plan in this offer. This ID will be visible to customers in the product URL and Azure Resource Manager templates (if applicable)"+ - "

Note: This must correspond to the Plan ID specified on the Plan overview tab of the Partner Center.", - planName:"Plan Name - Create a unique name for each plan in this offer. The plan name is used to differentiate software plans that may be a part of the same offer."+ - "

Note: This must correspond to the Plan name specified on the Plan overview tab of the Partner Center.", - restrictedAudience:"Restricted Audience (Tenant IDs) - Assign the audience that will have access to this private plan."+ - "

Note: This must correspond to the Restricted Audience specified on the Plan overview tab (Plan audience, check “This is a private plan.”) of the partner center." + planId: "Plan ID - Create a unique plan ID for each plan in this offer. This ID will be visible to customers in the product URL and Azure Resource Manager templates (if applicable)" + + "

Note: This must correspond to the Plan ID specified on the Plan overview tab of the Partner Center.", + planName: "Plan Name - Create a unique name for each plan in this offer. The plan name is used to differentiate software plans that may be a part of the same offer." + + "

Note: This must correspond to the Plan name specified on the Plan overview tab of the Partner Center.", + restrictedAudience: "Restricted Audience (Tenant IDs) - Assign the audience that will have access to this private plan." + + "

Note: This must correspond to the Restricted Audience specified on the Plan overview tab (Plan audience, check “This is a private plan.”) of the partner center." } } -export const Products= +export const ProductMessages = { - product:{ - ProductId:'', - ProductType:'', - HostType:'', - Owner:'' - }, + product: { + ProductId: '', + ProductType: '', + HostType: '', + Owner: '' + }, + deployment: { + DeploymentName: '', + Description: '' + }, + Version: { + DeploymentName: '', + VersionName:'', + Source:'', + RealtimePredictAPI:'', + TrainingAPI:'', + BatchInferenceAPI:'', + DeployEndpointAPI:'', + Authentication:'', + AMLWorkspace:'', + Key:'', + AdvancedSettings: '', + ProjectFile:'', + }, + AMLWorkSpace: + { + WorkspaceName:'', + ResourceId:'', + AADApplicationId:'', + AADTenantId:'', + AADApplicationSecret:'', + + }, + LunaWebHookURL: + { + HeaderTitle:'You can configure webhooks in Luna service and manage Luna.AI API subscription through Azure Marketplace SaaS offers.' + +'Please see Luna documentation for how to configure webhooks in your SaaS offer​', + SubscribewebhookURL: window.Configs.API_ENDPOINT + 'apisubscriptions/createwithid?ProductName={}&DeploymentName={}&UserId={}&SubscriptionName={}&SubscriptionId={}', + UnSubscribewebhookURL: window.Configs.API_ENDPOINT + 'apisubscriptions/delete?SubscriptionId={}', + SuspendwebhookURL: window.Configs.API_ENDPOINT + 'apisubscriptions/suspend?SubscriptionId={}', + } } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/routes.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/routes.ts index ffdfb54..73ba6ba 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/routes.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/constants/routes.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + export enum WebRoute { Home = '/', NoVersion = '/NoVersion', @@ -14,6 +17,6 @@ export enum WebRoute { LandingPage = '/LandingPage', SubscriptionDetail = '/SubscriptionDetail/:offerName/:subscriptionId', Products = '/Products', - ModifyProductInfo = '/ModifyProduct/:productId/Info', - ProductDetail= '/ModifyProduct/:productId/ProductDetail' + ModifyProductInfo = '/ModifyProduct/:productName/Info', + ProductDetail= '/ModifyProduct/:productName/ProductDetail' } \ No newline at end of file diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/formUtils/utils.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/formUtils/utils.ts index a31e6ac..ea6a2fc 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/formUtils/utils.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/shared/formUtils/utils.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {useState} from 'react'; import {Result} from "../../models"; @@ -114,7 +117,7 @@ export const handleSubmissionErrorsGeneral = (setErrors: any, setSubmitting: any // need to handle multiple validation errors and or validation and method errors from the server //TODO address this //let key = Object.keys(err)[0]; // field with validation errors' - debugger; + let errorDetailItem = err; let key = errorDetailItem.target.replace('$.', ''); diff --git a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/types/yup/index.d.ts b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/types/yup/index.d.ts index 1bc5187..b351775 100644 --- a/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/types/yup/index.d.ts +++ b/end-to-end-solutions/Luna/src/Luna.UI/isv_client/src/types/yup/index.d.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import {ArraySchema, ObjectSchema, default as yup} from 'yup' declare module 'yup' { diff --git a/end-to-end-solutions/Luna/src/Luna.WebJobs/Program.cs b/end-to-end-solutions/Luna/src/Luna.WebJobs/Program.cs index 1b83eee..0242008 100644 --- a/end-to-end-solutions/Luna/src/Luna.WebJobs/Program.cs +++ b/end-to-end-solutions/Luna/src/Luna.WebJobs/Program.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.IO; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Luna.WebJobs/TaskRunner.cs b/end-to-end-solutions/Luna/src/Luna.WebJobs/TaskRunner.cs index 0e9217f..2fb3d53 100644 --- a/end-to-end-solutions/Luna/src/Luna.WebJobs/TaskRunner.cs +++ b/end-to-end-solutions/Luna/src/Luna.WebJobs/TaskRunner.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/Extensions.cs b/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/Extensions.cs index 56a16e7..9a35885 100644 --- a/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/Extensions.cs +++ b/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/Extensions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using System; using System.Collections.Generic; using System.Text; diff --git a/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/HttpContextExtensions.cs b/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/HttpContextExtensions.cs index 36ae973..29d05e3 100644 --- a/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/HttpContextExtensions.cs +++ b/end-to-end-solutions/Luna/src/Microsoft.Identity.Web/HttpContextExtensions.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + using Microsoft.AspNetCore.Http; using System.IdentityModel.Tokens.Jwt; diff --git a/end-to-end-solutions/kma/Deployment/Deploy.ps1 b/end-to-end-solutions/kma/Deployment/Deploy.ps1 index d8966b1..e589ddc 100644 --- a/end-to-end-solutions/kma/Deployment/Deploy.ps1 +++ b/end-to-end-solutions/kma/Deployment/Deploy.ps1 @@ -1,3 +1,6 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. + param ( [Parameter(Mandatory=$true)] [string]$uniqueName = "default", diff --git a/end-to-end-solutions/kma/Deployment/Setup.ps1 b/end-to-end-solutions/kma/Deployment/Setup.ps1 index 84b85db..b5552c2 100644 --- a/end-to-end-solutions/kma/Deployment/Setup.ps1 +++ b/end-to-end-solutions/kma/Deployment/Setup.ps1 @@ -1,2 +1,5 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. + Install-Module -Name Az -AllowClobber Install-Module -Name Az.Search -AllowClobber \ No newline at end of file diff --git a/end-to-end-solutions/kma/test-scripts/AppSettings.ps1 b/end-to-end-solutions/kma/test-scripts/AppSettings.ps1 index 3f76ecc..2bc7cce 100644 --- a/end-to-end-solutions/kma/test-scripts/AppSettings.ps1 +++ b/end-to-end-solutions/kma/test-scripts/AppSettings.ps1 @@ -1,3 +1,6 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. + param ( [string]$uniqueName = "default", [string]$resourceGroup = "default", diff --git a/end-to-end-solutions/kma/test-scripts/SearchSettings.ps1 b/end-to-end-solutions/kma/test-scripts/SearchSettings.ps1 index 62d7752..60b44ed 100644 --- a/end-to-end-solutions/kma/test-scripts/SearchSettings.ps1 +++ b/end-to-end-solutions/kma/test-scripts/SearchSettings.ps1 @@ -1,3 +1,6 @@ +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. + param ( [string]$resourceGroup = "default", [string]$subscriptionId = "default",