sources/Google.Solutions.Apis/Compute/ComputeEngineClient.cs (403 lines of code) (raw):

// // Copyright 2020 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Apis.Compute.v1; using Google.Apis.Compute.v1.Data; using Google.Apis.Requests; using Google.Solutions.Apis.Auth; using Google.Solutions.Apis.Client; using Google.Solutions.Apis.Locator; using Google.Solutions.Common.Diagnostics; using Google.Solutions.Common.Text; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Google.Solutions.Apis.Compute { public sealed class ComputeEngineClient : ApiClientBase, IComputeEngineClient, IDisposable { private readonly ComputeService service; //--------------------------------------------------------------------- // Ctor. //--------------------------------------------------------------------- public ComputeEngineClient( ServiceEndpoint<ComputeEngineClient> endpoint, IAuthorization authorization, UserAgent userAgent) : base(endpoint, authorization, userAgent) { this.service = new ComputeService(this.Initializer); } public static ServiceEndpoint<ComputeEngineClient> CreateEndpoint( ServiceRoute? route = null) { return new ServiceEndpoint<ComputeEngineClient>( route ?? ServiceRoute.Public, "https://compute.googleapis.com/compute/v1/"); } //--------------------------------------------------------------------- // IComputeEngineClient. //--------------------------------------------------------------------- //--------------------------------------------------------------------- // Projects. //--------------------------------------------------------------------- public async Task<Project> GetProjectAsync( ProjectLocator project, CancellationToken cancellationToken) { using (ApiTraceSource.Log.TraceMethod().WithParameters(project)) { try { return await this.service.Projects .Get(project.Name).ExecuteAsync(cancellationToken) .ConfigureAwait(false); } catch (GoogleApiException e) when (e.IsAccessDeniedByVpcServiceControlPolicy()) { throw new ResourceAccessDeniedByVpcScPolicyException(e); } catch (GoogleApiException e) when (e.IsAccessDenied()) { throw new ResourceAccessDeniedException( $"You do not have sufficient permissions to access project {project.Name}. " + "You need the 'Compute Viewer' role (or an equivalent custom role) " + "to perform this action.", HelpTopics.ProjectAccessControl, e); } catch (GoogleApiException e) when ( e.IsNotFound() || e.IsBadRequest()) { // // NB. The API may return a 400 instead of a 404. // throw new ResourceNotFoundException( $"The project {project.Name} does not exist", e); } } } //--------------------------------------------------------------------- // Instances. //--------------------------------------------------------------------- public async Task<IEnumerable<Instance>> ListInstancesAsync( ProjectLocator project, CancellationToken cancellationToken) { using (ApiTraceSource.Log.TraceMethod().WithParameters(project)) { try { var request = this.service.Instances.AggregatedList(project.Name); // // Ignore zones or regions that are currently unavailable, // cf. go/drd-cp. // request.ReturnPartialSuccess = true; var instancesByZone = await new PageStreamer< InstancesScopedList, InstancesResource.AggregatedListRequest, InstanceAggregatedList, string>( (req, token) => req.PageToken = token, response => response.NextPageToken, response => response.Items.Values.Where(v => v != null)) .FetchAllAsync( request, cancellationToken) .ConfigureAwait(false); var result = instancesByZone .Where(z => z.Instances != null) // API returns null for empty zones. .SelectMany(zone => zone.Instances); ApiTraceSource.Log.TraceVerbose("Found {0} instances", result.Count()); return result; } catch (GoogleApiException e) when (e.IsAccessDeniedByVpcServiceControlPolicy()) { throw new ResourceAccessDeniedByVpcScPolicyException(e); } catch (GoogleApiException e) when (e.IsAccessDenied()) { throw new ResourceAccessDeniedException( "You do not have sufficient permissions to list VM instances in " + $"project {project.Name}. " + "You need the 'Compute Viewer' role (or an equivalent custom role) " + "to perform this action.", HelpTopics.ProjectAccessControl, e); } catch (GoogleApiException e) when (e.IsNotFound()) { throw new ResourceNotFoundException( $"The project {project.Name} does not exist", e); } } } public async Task<IEnumerable<Instance>> ListInstancesAsync( ZoneLocator zoneLocator, CancellationToken cancellationToken) { using (ApiTraceSource.Log.TraceMethod().WithParameters(zoneLocator)) { try { var result = await new PageStreamer< Instance, InstancesResource.ListRequest, InstanceList, string>( (req, token) => req.PageToken = token, response => response.NextPageToken, response => response.Items) .FetchAllAsync( this.service.Instances.List(zoneLocator.ProjectId, zoneLocator.Name), cancellationToken) .ConfigureAwait(false); ApiTraceSource.Log.TraceVerbose("Found {0} instances", result.Count); return result; } catch (GoogleApiException e) when (e.IsAccessDeniedByVpcServiceControlPolicy()) { throw new ResourceAccessDeniedByVpcScPolicyException(e); } catch (GoogleApiException e) when (e.IsAccessDenied()) { throw new ResourceAccessDeniedException( "You do not have sufficient permissions to list VM instances in " + $"project {zoneLocator.ProjectId}. " + "You need the 'Compute Viewer' role (or an equivalent custom role) " + "to perform this action.", HelpTopics.ProjectAccessControl, e); } catch (GoogleApiException e) when (e.IsNotFound()) { throw new ResourceNotFoundException( $"The zone {zoneLocator} does not exist", e); } } } public async Task<Instance> GetInstanceAsync( InstanceLocator instance, CancellationToken cancellationToken) { using (ApiTraceSource.Log.TraceMethod().WithParameters(instance)) { try { return await this.service.Instances.Get( instance.ProjectId, instance.Zone, instance.Name).ExecuteAsync(cancellationToken) .ConfigureAwait(false); } catch (GoogleApiException e) when (e.IsAccessDeniedByVpcServiceControlPolicy()) { throw new ResourceAccessDeniedByVpcScPolicyException(e); } catch (GoogleApiException e) when (e.IsAccessDenied()) { throw new ResourceAccessDeniedException( "You do not have sufficient permissions to access " + $"VM instance {instance.Name} in project {instance.ProjectId}", HelpTopics.ProjectAccessControl, e); } catch (GoogleApiException e) when (e.IsNotFound()) { throw new ResourceNotFoundException( $"The VM instance {instance.Name} does not exist " + $"in project {instance.ProjectId}", e); } } } //--------------------------------------------------------------------- // Guest attributes. //--------------------------------------------------------------------- public async Task<GuestAttributes?> GetGuestAttributesAsync( InstanceLocator instance, string queryPath, CancellationToken cancellationToken) { using (ApiTraceSource.Log.TraceMethod().WithParameters(instance)) { try { var request = this.service.Instances.GetGuestAttributes( instance.ProjectId, instance.Zone, instance.Name); request.QueryPath = queryPath; return await request .ExecuteAsync(cancellationToken) .ConfigureAwait(false); } catch (GoogleApiException e) when (e.IsNotFound()) { // No guest attributes present. return null; } catch (GoogleApiException e) when (e.IsAccessDenied()) { throw new ResourceAccessDeniedException( "You do not have sufficient permissions to access the guest attributes " + $"of VM instance {instance.Name} in project {instance.ProjectId}", HelpTopics.ProjectAccessControl, e); } } } //--------------------------------------------------------------------- // Serial port. //--------------------------------------------------------------------- public IAsyncReader<string> GetSerialPortOutput( InstanceLocator instanceRef, ushort portNumber) { using (ApiTraceSource.Log.TraceMethod().WithParameters(instanceRef)) { return new SerialPortReader( this.service.Instances, instanceRef, portNumber); } } //--------------------------------------------------------------------- // Metadata. //--------------------------------------------------------------------- public async Task UpdateMetadataAsync( InstanceLocator instance, Action<Metadata> updateMetadata, CancellationToken token) { using (ApiTraceSource.Log.TraceMethod().WithParameters(instance)) { try { await this.service.Instances.UpdateMetadataAsync( instance, updateMetadata, token) .ConfigureAwait(false); } catch (GoogleApiException e) when (e.IsAccessDenied() || e.Error == null) { // // NB. Sometimes the error info is missing in 403 errors. // throw new ResourceAccessDeniedException( $"You don't have sufficient permissions to modify " + $"the metadata of VM instance {instance.Name} in project " + $"{instance.ProjectId}", HelpTopics.ProjectAccessControl, e); } catch (GoogleApiException e) when (e.IsNotFound()) { throw new ResourceNotFoundException( $"The VM instance {instance.Name} does not exist " + $"in project {instance.ProjectId}", e); } } } public async Task UpdateCommonInstanceMetadataAsync( ProjectLocator project, Action<Metadata> updateMetadata, CancellationToken token) { using (ApiTraceSource.Log.TraceMethod().WithParameters(project)) { try { await this.service.Projects .UpdateMetadataAsync( project.Name, updateMetadata, token) .ConfigureAwait(false); } catch (GoogleApiException e) when (e.IsAccessDenied() || e.Error == null) { // // NB. Sometimes the error info is missing in 403 errors. // throw new ResourceAccessDeniedException( "You don't have sufficient permissions to modify " + $"the metadata of project {project.Name}", HelpTopics.ProjectAccessControl, e); } catch (GoogleApiException e) when (e.IsNotFound()) { throw new ResourceNotFoundException( $"The project {project.Name} does not exist", e); } } } //--------------------------------------------------------------------- // Permission check. //--------------------------------------------------------------------- public async Task<bool> IsAccessGrantedAsync( InstanceLocator instanceLocator, string permission) { using (ApiTraceSource.Log.TraceMethod().WithParameters(permission)) { try { var response = await this.service.Instances .TestIamPermissions( new TestPermissionsRequest { Permissions = new[] { permission } }, instanceLocator.ProjectId, instanceLocator.Zone, instanceLocator.Name) .ExecuteAsync() .ConfigureAwait(false); return response != null && response.Permissions != null && response.Permissions.Any(p => p == permission); } catch (Exception e) when (e.IsAccessDeniedError()) { // // NB. testPermission requires the 'compute.instances.list' // permission. Fail open if the caller does not have that // permission. // ApiTraceSource.Log.TraceWarning( "Permission check failed because caller does not have " + "the permission to test permissions"); return true; } } } //--------------------------------------------------------------------- // Control instance lifecycle. //--------------------------------------------------------------------- public async Task ControlInstanceAsync( InstanceLocator instance, InstanceControlCommand command, CancellationToken cancellationToken) { using (ApiTraceSource.Log.TraceMethod() .WithParameters(instance, command)) { try { ClientServiceRequest<Operation> request = command switch { InstanceControlCommand.Start => this.service.Instances.Start( instance.ProjectId, instance.Zone, instance.Name), InstanceControlCommand.Stop => this.service.Instances.Stop( instance.ProjectId, instance.Zone, instance.Name), InstanceControlCommand.Suspend => this.service.Instances.Suspend( instance.ProjectId, instance.Zone, instance.Name), InstanceControlCommand.Resume => this.service.Instances.Resume( instance.ProjectId, instance.Zone, instance.Name), InstanceControlCommand.Reset => this.service.Instances.Reset( instance.ProjectId, instance.Zone, instance.Name), _ => throw new ArgumentException("The command is not supported"), }; await request .ExecuteAndAwaitOperationAsync( instance.ProjectId, cancellationToken) .ConfigureAwait(false); } catch (GoogleApiException e) when (e.IsNotFound()) { throw new ResourceNotFoundException( $"The VM instance {instance.Name} does not exist " + $"in project {instance.ProjectId}", e); } catch (GoogleApiException e) when (e.IsAccessDenied()) { throw new ResourceAccessDeniedException( "You do not have sufficient permissions to control the " + $"VM instance {instance.Name} in project {instance.ProjectId}", HelpTopics.ProjectAccessControl, e); } } } //--------------------------------------------------------------------- // IDisposable. //--------------------------------------------------------------------- public void Dispose() { this.service.Dispose(); } } }