1# -*- coding: utf-8 -*- # 2# Copyright 2016 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Utilities for the API to configure cross-project networking (XPN).""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21from apitools.base.py import list_pager 22from googlecloudsdk.api_lib.compute import base_classes 23from googlecloudsdk.api_lib.compute import exceptions 24from googlecloudsdk.api_lib.compute import utils 25 26 27_DEFAULT_API_VERSION = 'v1' 28 29 30class XpnApiError(exceptions.Error): 31 pass 32 33 34class XpnClient(object): 35 """A client for interacting with the cross-project networking (XPN) API. 36 37 The XPN API is a subset of the Google Compute Engine API. 38 """ 39 40 def __init__(self, compute_client): 41 self.compute_client = compute_client 42 self.client = compute_client.apitools_client 43 self.messages = compute_client.messages 44 45 # TODO(b/30465957): Refactor to use apitools clients directly and not the 46 # compute utilities 47 def _MakeRequest(self, request, errors): 48 return self.compute_client.MakeRequests( 49 requests=[request], 50 errors_to_collect=errors) 51 52 def _MakeRequestSync(self, request_tuple, operation_msg=None): 53 errors = [] 54 results = list(self._MakeRequest(request_tuple, errors)) 55 56 if errors: 57 operation_msg = operation_msg or 'complete all requests' 58 msg = 'Could not {0}:'.format(operation_msg) 59 utils.RaiseException(errors, XpnApiError, msg) 60 61 return results[0] # if there were no errors, this will exist 62 63 def EnableHost(self, project): 64 """Enable the project with the given ID as an XPN host.""" 65 request_tuple = ( 66 self.client.projects, 67 'EnableXpnHost', 68 self.messages.ComputeProjectsEnableXpnHostRequest(project=project)) 69 msg = 'enable [{project}] as XPN host'.format(project=project) 70 self._MakeRequestSync(request_tuple, msg) 71 72 def DisableHost(self, project): 73 """Disable the project with the given ID as an XPN host.""" 74 request_tuple = ( 75 self.client.projects, 76 'DisableXpnHost', 77 self.messages.ComputeProjectsDisableXpnHostRequest(project=project)) 78 msg = 'disable [{project}] as XPN host'.format(project=project) 79 self._MakeRequestSync(request_tuple, msg) 80 81 def GetHostProject(self, project): 82 """Get the XPN host for the given project.""" 83 request_tuple = ( 84 self.client.projects, 85 'GetXpnHost', 86 self.messages.ComputeProjectsGetXpnHostRequest(project=project)) 87 msg = 'get XPN host for project [{project}]'.format(project=project) 88 return self._MakeRequestSync(request_tuple, msg) 89 90 def ListEnabledResources(self, project): 91 request = self.messages.ComputeProjectsGetXpnResourcesRequest( 92 project=project) 93 return list_pager.YieldFromList( 94 self.client.projects, 95 request, 96 method='GetXpnResources', 97 batch_size_attribute='maxResults', 98 batch_size=500, 99 field='resources') 100 101 def ListOrganizationHostProjects(self, project, organization_id): 102 """List the projects in an organization that are enabled as XPN hosts. 103 104 Args: 105 project: str, project ID to make the request with. 106 organization_id: str, the ID of the organization to list XPN hosts 107 for. If None, the organization is inferred from the project. 108 109 Returns: 110 Generator for `Project`s corresponding to XPN hosts in the organization. 111 """ 112 request = self.messages.ComputeProjectsListXpnHostsRequest( 113 project=project, 114 projectsListXpnHostsRequest=self.messages.ProjectsListXpnHostsRequest( 115 organization=organization_id)) 116 return list_pager.YieldFromList( 117 self.client.projects, 118 request, 119 method='ListXpnHosts', 120 batch_size_attribute='maxResults', 121 batch_size=500, 122 field='items') 123 124 def _EnableXpnAssociatedResource(self, host_project, associated_resource, 125 xpn_resource_type): 126 """Associate the given resource with the given XPN host project. 127 128 Args: 129 host_project: str, ID of the XPN host project 130 associated_resource: ID of the resource to associate with host_project 131 xpn_resource_type: XpnResourceId.TypeValueValuesEnum, the type of the 132 resource 133 """ 134 projects_enable_request = self.messages.ProjectsEnableXpnResourceRequest( 135 xpnResource=self.messages.XpnResourceId( 136 id=associated_resource, 137 type=xpn_resource_type) 138 ) 139 request = self.messages.ComputeProjectsEnableXpnResourceRequest( 140 project=host_project, 141 projectsEnableXpnResourceRequest=projects_enable_request) 142 request_tuple = (self.client.projects, 'EnableXpnResource', request) 143 msg = ('enable resource [{0}] as an associated resource ' 144 'for project [{1}]').format(associated_resource, host_project) 145 self._MakeRequestSync(request_tuple, msg) 146 147 def EnableXpnAssociatedProject(self, host_project, associated_project): 148 """Associate the given project with the given XPN host project. 149 150 Args: 151 host_project: str, ID of the XPN host project 152 associated_project: ID of the project to associate 153 """ 154 xpn_types = self.messages.XpnResourceId.TypeValueValuesEnum 155 self._EnableXpnAssociatedResource( 156 host_project, associated_project, xpn_resource_type=xpn_types.PROJECT) 157 158 def _DisableXpnAssociatedResource(self, host_project, associated_resource, 159 xpn_resource_type): 160 """Disassociate the given resource from the given XPN host project. 161 162 Args: 163 host_project: str, ID of the XPN host project 164 associated_resource: ID of the resource to disassociate from host_project 165 xpn_resource_type: XpnResourceId.TypeValueValuesEnum, the type of the 166 resource 167 """ 168 projects_disable_request = self.messages.ProjectsDisableXpnResourceRequest( 169 xpnResource=self.messages.XpnResourceId( 170 id=associated_resource, 171 type=xpn_resource_type) 172 ) 173 request = self.messages.ComputeProjectsDisableXpnResourceRequest( 174 project=host_project, 175 projectsDisableXpnResourceRequest=projects_disable_request) 176 request_tuple = (self.client.projects, 'DisableXpnResource', request) 177 msg = ('disable resource [{0}] as an associated resource ' 178 'for project [{1}]').format(associated_resource, host_project) 179 self._MakeRequestSync(request_tuple, msg) 180 181 def DisableXpnAssociatedProject(self, host_project, associated_project): 182 """Disassociate the given project from the given XPN host project. 183 184 Args: 185 host_project: str, ID of the XPN host project 186 associated_project: ID of the project to disassociate from host_project 187 """ 188 xpn_types = self.messages.XpnResourceId.TypeValueValuesEnum 189 self._DisableXpnAssociatedResource( 190 host_project, associated_project, xpn_resource_type=xpn_types.PROJECT) 191 192 193def GetXpnClient(release_track): 194 holder = base_classes.ComputeApiHolder(release_track) 195 return XpnClient(holder.client) 196