Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Videointelligence: Add VPC SC integration tests. #8607

Merged
merged 15 commits into from
Jul 11, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import google.api_core.operations_v1

from google.cloud.videointelligence_v1.proto import video_intelligence_pb2_grpc
from google.longrunning import operations_pb2
mkudejim marked this conversation as resolved.
Show resolved Hide resolved


class VideoIntelligenceServiceGrpcTransport(object):
Expand Down Expand Up @@ -71,7 +72,8 @@ def __init__(
self._stubs = {
"video_intelligence_service_stub": video_intelligence_pb2_grpc.VideoIntelligenceServiceStub(
channel
)
),
"operations_stub": operations_pb2.OperationsStub(channel)
mkudejim marked this conversation as resolved.
Show resolved Hide resolved
}

# Because this API includes a method that returns a
Expand Down Expand Up @@ -113,6 +115,18 @@ def channel(self):
"""
return self._channel

@property
def get_operation(self):
return self._operations_client.get_operation

@property
def delete_operation(self):
return self._operations_client.delete_operation

@property
def cancel_operation(self):
return self._operations_client.cancel_operation

mkudejim marked this conversation as resolved.
Show resolved Hide resolved
@property
def annotate_video(self):
"""Return the gRPC stub for :meth:`VideoIntelligenceServiceClient.annotate_video`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import warnings

from google.oauth2 import service_account
import google.api_core.client_options
mkudejim marked this conversation as resolved.
Show resolved Hide resolved
import google.api_core.gapic_v1.client_info
import google.api_core.gapic_v1.config
import google.api_core.gapic_v1.method
Expand Down Expand Up @@ -83,7 +82,6 @@ def __init__(
credentials=None,
client_config=None,
client_info=None,
client_options=None,
mkudejim marked this conversation as resolved.
Show resolved Hide resolved
):
"""Constructor.

Expand Down Expand Up @@ -114,9 +112,6 @@ def __init__(
API requests. If ``None``, then default info will be used.
Generally, you only need to set this if you're developing
your own client library.
client_options (Union[dict, google.api_core.client_options.ClientOptions]):
Client options used to set user options on the client. API Endpoint
should be set through client_options.
Copy link
Contributor

Choose a reason for hiding this comment

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

This change reverts #8528, and is in generated code.

"""
# Raise deprecation warnings for things we want to go away.
if client_config is not None:
Expand All @@ -135,15 +130,6 @@ def __init__(
stacklevel=2,
)

api_endpoint = self.SERVICE_ADDRESS
if client_options:
if type(client_options) == dict:
client_options = google.api_core.client_options.from_dict(
client_options
)
if client_options.api_endpoint:
api_endpoint = client_options.api_endpoint

Copy link
Contributor

Choose a reason for hiding this comment

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

This change reverts #8528, and is in generated code.

# Instantiate the transport.
# The transport is responsible for handling serialization and
# deserialization and actually sending data to the service.
Expand All @@ -152,7 +138,6 @@ def __init__(
self.transport = transport(
credentials=credentials,
default_class=video_intelligence_service_grpc_transport.VideoIntelligenceServiceGrpcTransport,
address=api_endpoint,
mkudejim marked this conversation as resolved.
Show resolved Hide resolved
)
else:
if credentials:
Expand All @@ -163,7 +148,7 @@ def __init__(
self.transport = transport
else:
self.transport = video_intelligence_service_grpc_transport.VideoIntelligenceServiceGrpcTransport(
address=api_endpoint, channel=channel, credentials=credentials
address=self.SERVICE_ADDRESS, channel=channel, credentials=credentials
mkudejim marked this conversation as resolved.
Show resolved Hide resolved
)

if client_info is None:
Expand All @@ -187,7 +172,19 @@ def __init__(
# transport methods, wrapped with `wrap_method` to add retry,
# timeout, and the like.
self._inner_api_calls = {}

def channel(self):
return self.transport.channel
mkudejim marked this conversation as resolved.
Show resolved Hide resolved

def get_operation(self, name=None):
return self.transport.get_operation(name)

def delete_operation(self, name=None):
return self.transport.delete_operation(name)

def cancel_operation(self, name=None):
return self.transport.cancel_operation(name)
mkudejim marked this conversation as resolved.
Show resolved Hide resolved

# Service calls
def annotate_video(
self,
Expand Down Expand Up @@ -304,3 +301,5 @@ def annotate_video(
video_intelligence_pb2.AnnotateVideoResponse,
metadata_type=video_intelligence_pb2.AnnotateVideoProgress,
)


mkudejim marked this conversation as resolved.
Show resolved Hide resolved
159 changes: 159 additions & 0 deletions videointelligence/tests/system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Copyright 2017, Google LLC All rights reserved.
#
# Licensed 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.

"""System tests for VideoIntelligence API."""

import grpc
import io
import json
import os
import requests
import time
import unittest

import google.api_core.exceptions
from google.cloud import exceptions
from google.cloud import videointelligence
from google.cloud.videointelligence_v1 import enums

PROJECT_NUMBER = os.environ.get("PROJECT_NUMBER")
PROJECT_OUTSIDE = os.environ.get("GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_PERIMETER_PROJECT")
BUCKET_OUTSIDE = os.environ.get("GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_PERIMETER_BUCKET")
OUTSIDE_PROJECT_API_KEY = os.environ.get("GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_PERIMETER_PROJECT_API_KEY")
OUTSIDE_IP = os.environ.get("GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_IP")
INSIDE_IP = os.environ.get("GOOGLE_CLOUD_TESTS_VPCSC_INSIDE_IP")

class VideoIntelligenceSystemTestBase(unittest.TestCase):
client = None

def setUp(self):
self.to_delete_by_case = []
self.input_uri = "gs://cloud-samples-data/video/cat.mp4"

def tearDown(self):
for value in self.to_delete_by_case:
value.delete()


def setUpModule():
VideoIntelligenceSystemTestBase.client = videointelligence.VideoIntelligenceServiceClient()


class TestVideoIntelligenceClient(VideoIntelligenceSystemTestBase):
def test_annotate_video(self):
features_element = enums.Feature.LABEL_DETECTION
features = [features_element]
response = self.client.annotate_video(input_uri=self.input_uri, features=features)

# Wait for the operation to complete.
lro_timeout_seconds = 60
start_time = time.time()
cnt = 0
while not response.done() and (time.time() - start_time) < lro_timeout_seconds:
time.sleep(1)
cnt += 1
if not response.done():
self.fail(
"wait for operation timed out after {lro_timeout_seconds} seconds".format(
lro_timeout_seconds=lro_timeout_seconds
)
)

result = response.result()
annotations = result.annotation_results[0]
assert len(annotations.segment_label_annotations) > 0



@unittest.skipUnless(
OUTSIDE_PROJECT_API_KEY,
"GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_PERIMETER_PROJECT_API_KEY not set in environment.",
)
class TestVideoIntelligenceClientVpcSc(VideoIntelligenceSystemTestBase):
# Tests to verify VideoIntelligence service requests blocked when trying to access resources outside of a secure perimeter.
def setUp(self):
VideoIntelligenceSystemTestBase.setUp(self)
# api-endpoint
self.url = "https://videointelligence.googleapis.com/v1/videos:annotate?key={}".format(OUTSIDE_PROJECT_API_KEY)
self.body = {"input_uri": self.input_uri, "features": ["LABEL_DETECTION"], "location_id": "us-west1"}

def test_get_operation_from_different_project(self):
# sending request and saving the response as response object
r = requests.post(url=self.url, data=self.body)
outside_project_operation = json.loads(r.text)

# Uses inside perimeter project to access operation resource created owned by outside project
with self.assertRaises(google.api_core.exceptions.NotFound) as cm:
self.client.get_operation(outside_project_operation["name"])

# Assert it returns not found.
self.assertEqual(cm.exception.code, 404)
self.assertEqual(cm.exception.message, "Operation not found: {}".format(outside_project_operation["name"]))

def test_delete_operation_from_different_project(self):
# sending request and saving the response as response object
r = requests.post(url=self.url, data=self.body)
outside_project_operation = json.loads(r.text)

# Uses inside perimeter project to access operation resource created owned by outside project
with self.assertRaises(Exception) as cm:
self.client.delete_operation(outside_project_operation["name"])

# Assert it returns not found.
self.assertEqual(cm.exception.code, 404)
self.assertEqual(cm.exception.message, "Operation not found: {}".format(outside_project_operation["name"]))

def test_cancel_operation_from_different_project(self):
# sending request and saving the response as response object
r = requests.post(url=self.url, data=self.body)
outside_project_operation = json.loads(r.text)

# Uses inside perimeter project to access operation resource created owned by outside project
with self.assertRaises(Exception) as cm:
self.client.cancel_operation(outside_project_operation["name"])

# Assert it returns not found.
self.assertEqual(cm.exception.code, 404)
self.assertEqual(cm.exception.message, "Operation not found: {}".format(outside_project_operation["name"]))

@unittest.skipUnless(PROJECT_NUMBER, "PROJECT_NUMBER not set in environment.")
@unittest.skipUnless(OUTSIDE_IP, "GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_IP not set in environment.")
def test_outside_ip_address_blocked(self):
headers = {
"Content-Type": "application/json",
"X-User-IP": OUTSIDE_IP,
"X-Google-GFE-Cloud-Client-Network-Project-Number": PROJECT_NUMBER,
}
r = requests.post(url=self.url, data=json.dumps(self.body), headers=headers)
outside_project_operation = json.loads(r.text)
print(outside_project_operation)
# Assert it returns permission denied from VPC SC
self.assertEqual(outside_project_operation["error"]["code"], 403)
self.assertEqual(outside_project_operation["error"]["status"], "PERMISSION_DENIED")
self.assertEqual(outside_project_operation["error"]["details"][0]["violations"][0]["type"], "VPC_SERVICE_CONTROLS")
self.assertEqual(outside_project_operation["error"]["message"], "Request is prohibited by organization's policy")

@unittest.skipUnless(PROJECT_NUMBER, "PROJECT_NUMBER not set in environment.")
@unittest.skipUnless(INSIDE_IP, "GOOGLE_CLOUD_TESTS_VPCSC_INSIDE_IP not set in environment.")
def test_inside_ip_address_allowed(self):
headers = {
"Content-Type": "application/json",
"X-User-IP": INSIDE_IP,
"X-Google-GFE-Cloud-Client-Network-Project-Number": PROJECT_NUMBER,
}
r = requests.post(url=self.url, data=json.dumps(self.body), headers=headers)
operation = json.loads(r.text)
# Assert it returns non-empty operation name.
self.assertNotEqual(operation["name"], "")