Пример #1
0
def test_submit_pubsub_server_error(
    integration_test: IntegrationTest,
    publisher: PublisherClient,
    pubsub: Any,
    topic: str,
):
    if pubsub == "google":
        pytest.skip("requires pubsub emulator")

    # override pubsub status
    publisher.update_topic({"name": topic}, {"paths": ["status_code=internal"]})
    integration_test.assert_accepted_and_queued()

    # restore pubsub status
    publisher.update_topic({"name": topic}, {"paths": ["status_code="]})
    integration_test.assert_flushed()
Пример #2
0
def get_pubsub_publisher() -> PublisherClient:
    global publisher

    if publisher is None:
        publisher = PublisherClient()

    return publisher
Пример #3
0
def init_app(app: Sanic) -> Tuple[PublisherClient, SQLiteAckQueue]:
    """Initialize Sanic app with url rules."""
    # Initialize PubSub client
    timeout = app.config.get("PUBLISH_TIMEOUT_SECONDS", None)
    client = PublisherClient()
    client.api.publish = partial(
        client.api.publish,
        retry=Retry(TRANSIENT_ERRORS, deadline=timeout),
        timeout=timeout,
    )
    client._batch_class = AsyncioBatch
    # Use a SQLiteAckQueue because:
    # * we use acks to ensure messages only removed on success
    # * persist-queue's SQLite*Queue is faster than its Queue
    # * SQLite provides thread-safe and process-safe access
    queue_config = {
        key[6:].lower(): value
        for key, value in app.config.items() if key.startswith("QUEUE_")
    }
    q = SQLiteAckQueue(**queue_config)
    # get metadata_headers config
    metadata_headers = app.config["METADATA_HEADERS"]
    # validate attribute keys
    for attribute in metadata_headers.values():
        if len(attribute.encode("utf8")) > 256:
            # https://cloud.google.com/pubsub/quotas#resource_limits
            raise ValueError(
                "Metadata attribute exceeds key size limit of 256 bytes")
    # add routes for ROUTE_TABLE
    handlers: Dict[str, Callable] = {}
    for route in app.config["ROUTE_TABLE"]:
        if route.topic not in handlers:
            # generate one handler per topic
            handlers[route.topic] = partial(
                submit,
                client=client,
                q=q,
                topic=route.topic,
                metadata_headers=metadata_headers,
            )
            handlers[route.topic].__name__ = f"submit({route.topic})"
        app.add_route(
            handler=handlers[route.topic],
            uri=route.uri,
            methods=[method.upper() for method in route.methods],
        )
    return client, q
Пример #4
0
def test_set_topic_policy(
    publisher_client: pubsub_v1.PublisherClient,
    topic_path: str,
) -> CaptureFixture:
    iam.set_topic_policy(PROJECT_ID, TOPIC_ID)
    policy = publisher_client.get_iam_policy(request={"resource": topic_path})
    assert "roles/pubsub.publisher" in str(policy)
    assert "allUsers" in str(policy)
Пример #5
0
    def tearDownClass(cls):
        super().tearDownClass()
        publisher = PublisherClient()
        subscriber = SubscriberClient()

        for topic in ["test0", "test1"]:
            try:
                publisher.delete_topic(publisher.topic_path(
                    cls.project, topic))
            except Exception as ex:
                raise ex

            try:
                subscriber.delete_subscription(
                    subscriber.subscription_path(cls.project, topic))
            except Exception as ex:
                raise ex
Пример #6
0
def publish_message(response_text, patient_id, event_attributes):
    publisher = PublisherClient()

    # pylint: disable=no-member
    topic_path = publisher.topic_path(
        os.environ.get("PROJECT_NAME"), os.environ.get("TOPIC_NAME")
    )
    data = response_text.encode("utf-8")
    future = publisher.publish(
        topic_path,
        data=data,
        **event_attributes,
        patient_id=patient_id,
    )
    message_id = future.result()
    logger.info(f"Successfully published message: {message_id}")
    print(f"Successfully published message: {message_id}")
Пример #7
0
def test_submit_pubsub_permission_denied(
    integration_test: IntegrationTest,
    publisher: PublisherClient,
    pubsub: str,
    topic: str,
):
    if pubsub == "google":
        pytest.skip("not implemented")
    else:
        publisher.update_topic({"name": topic},
                               {"paths": ["status_code=permission_denied"]})
    integration_test.assert_accepted_and_queued()
    if pubsub == "google":
        pytest.skip("not implemented")
    else:
        publisher.update_topic({"name": topic}, {"paths": ["status_code="]})
    integration_test.assert_flushed_and_delivered()
Пример #8
0
def _publisher(service_account_json: Union[str, None]) -> PublisherClient:
    try:
        return PublisherClient(
            credentials=_credentials(service_account_json, 'Publisher'),
            publisher_options=PublisherOptions(enable_message_ordering=True))
    except DefaultCredentialsError:
        logging.error(
            '`GOOGLE_APPLICATION_CREDENTIALS` environment variable or service account in `address` field are not set!'
        )
Пример #9
0
def test_submit_pubsub_timeout(
    integration_test: IntegrationTest,
    publisher: PublisherClient,
    pubsub: Any,
    topic: str,
):
    if pubsub == "google":
        pytest.skip("requires pubsub emulator")

    # override pubsub response time
    publisher.update_topic(
        {"name": topic},
        {"paths": ["sleep=%.1f" % (PUBLISH_TIMEOUT_SECONDS + 1)]})
    integration_test.assert_accepted_and_queued()

    # restore pubsub response time
    publisher.update_topic({"name": topic}, {"paths": ["sleep="]})
    integration_test.assert_flushed()
Пример #10
0
    def __init__(self,
                 name,
                 dist,
                 emulator_host,
                 emulator_port,
                 max_bytes,
                 max_latency,
                 max_messages,
                 ordering=False,
                 client_options=None,
                 credentials=None,
                 services_service=None,
                 **config):
        services_service(plugin.Plugin.__init__,
                         self,
                         name,
                         dist,
                         emulator_host=emulator_host,
                         emulator_port=emulator_port,
                         max_bytes=max_bytes,
                         max_latency=max_latency,
                         max_messages=max_messages,
                         ordering=False,
                         client_options=None,
                         credentials=None,
                         **config)

        batch_settings = types.BatchSettings(max_bytes=max_bytes,
                                             max_latency=max_latency,
                                             max_messages=max_messages)

        publisher_options = types.PublisherOptions(
            enable_message_ordering=ordering)

        settings = {}
        if client_options is not None:
            if isinstance(client_options, (str, type(u''))):
                client_options = services_service(
                    reference.load_object(client_options)[0])
            settings['client_options'] = client_options

        if emulator_host:
            channel = grpc.insecure_channel('{}:{}'.format(
                emulator_host, emulator_port))
            transport = PublisherGrpcTransport(channel=channel)
        else:
            transport = None

            if credentials is not None:
                settings['credentials'] = services_service(
                    reference.load_object(credentials)[0])

        self.__class__.proxy_target = PublisherClient(batch_settings,
                                                      publisher_options,
                                                      transport=transport,
                                                      **settings)
Пример #11
0
    def get_conn(self) -> PublisherClient:
        """
        Retrieves connection to Google Cloud Pub/Sub.

        :return: Google Cloud Pub/Sub client object.
        :rtype: google.cloud.pubsub_v1.PublisherClient
        """
        if not self._client:
            self._client = PublisherClient(credentials=self._get_credentials(), client_info=self.client_info)
        return self._client
Пример #12
0
def proto_topic(publisher_client: pubsub_v1.PublisherClient,
                proto_schema: str) -> Generator[str, None, None]:
    proto_topic_path = publisher_client.topic_path(PROJECT_ID, PROTO_TOPIC_ID)

    try:
        proto_topic = publisher_client.get_topic(
            request={"topic": proto_topic_path})
    except NotFound:
        proto_topic = publisher_client.create_topic(
            request={
                "name": proto_topic_path,
                "schema_settings": {
                    "schema": proto_schema,
                    "encoding": Encoding.BINARY,
                },
            })

    yield proto_topic.name

    publisher_client.delete_topic(request={"topic": proto_topic.name})
Пример #13
0
class PublishService:
    _publisher: PublisherClient
    _topic_name: str
    _request: Request

    def __init__(self, topic_name: str, request: Request):
        self._publisher = PublisherClient()
        self._topic_name = topic_name
        self._request = request

    def _publish_message(self, message_name, message):
        metadata = Gobits.from_request(request=self._request)
        try:
            my_gobits = [metadata.to_json()]
        except:  # noqa: E722
            my_gobits = []
        message_to_publish = {"gobits": my_gobits, message_name: message}
        self._publisher.publish(
            self._topic_name,
            bytes(json.dumps(message_to_publish).encode("utf-8")))
Пример #14
0
    def delete_topic(
        self,
        topic: str,
        project_id: Optional[str] = None,
        fail_if_not_exists: bool = False,
        retry: Optional[Retry] = None,
        timeout: Optional[float] = None,
        metadata: Optional[Sequence[Tuple[str, str]]] = None,
    ) -> None:
        """
        Deletes a Pub/Sub topic if it exists.

        :param topic: the Pub/Sub topic name to delete; do not
            include the ``projects/{project}/topics/`` prefix.
        :type topic: str
        :param project_id: Optional, the GCP project ID in which to delete the topic.
            If set to None or missing, the default project_id from the GCP connection is used.
        :type project_id: str
        :param fail_if_not_exists: if set, raise an exception if the topic
            does not exist
        :type fail_if_not_exists: bool
        :param retry: (Optional) A retry object used to retry requests.
            If None is specified, requests will not be retried.
        :type retry: google.api_core.retry.Retry
        :param timeout: (Optional) The amount of time, in seconds, to wait for the request
            to complete. Note that if retry is specified, the timeout applies to each
            individual attempt.
        :type timeout: float
        :param metadata: (Optional) Additional metadata that is provided to the method.
        :type metadata: Sequence[Tuple[str, str]]]
        """
        if not project_id:
            raise ValueError("Project ID should be set.")
        publisher = self.get_conn()
        topic_path = PublisherClient.topic_path(project_id, topic)  # pylint: disable=no-member

        self.log.info("Deleting topic (path) %s", topic_path)
        try:
            # pylint: disable=no-member
            publisher.delete_topic(
                topic=topic_path,
                retry=retry,
                timeout=timeout,
                metadata=metadata,
            )
        except NotFound:
            self.log.warning('Topic does not exist: %s', topic_path)
            if fail_if_not_exists:
                raise PubSubException(
                    'Topic does not exist: {}'.format(topic_path))
        except GoogleAPICallError as e:
            raise PubSubException('Error deleting topic {}'.format(topic), e)
        self.log.info("Deleted topic (path) %s", topic_path)
Пример #15
0
def avro_topic(publisher_client: pubsub_v1.PublisherClient,
               avro_schema: str) -> Generator[str, None, None]:
    from google.pubsub_v1.types import Encoding

    avro_topic_path = publisher_client.topic_path(PROJECT_ID, AVRO_TOPIC_ID)

    try:
        avro_topic = publisher_client.get_topic(
            request={"topic": avro_topic_path})
    except NotFound:
        avro_topic = publisher_client.create_topic(
            request={
                "name": avro_topic_path,
                "schema_settings": {
                    "schema": avro_schema,
                    "encoding": Encoding.BINARY,
                },
            })

    yield avro_topic.name

    publisher_client.delete_topic(request={"topic": avro_topic.name})
Пример #16
0
    def setUpClass(cls):
        super().setUpClass()

        cls.project = os.environ.get("PUBSUB_PROJECT", "testing")
        cls.credentials = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS",
                                         None)

        for subscription in ["test0", "test1"]:
            try:
                publisher = PublisherClient()
                publisher.create_topic(
                    publisher.topic_path(cls.project, subscription))
            except AlreadyExists:
                pass

            try:
                subscriber = SubscriberClient()
                subscriber.create_subscription(
                    subscriber.subscription_path(cls.project, subscription),
                    subscriber.topic_path(cls.project, subscription))
            except AlreadyExists:
                pass
Пример #17
0
def test_flush(
    integration_test: IntegrationTest,
    publisher: PublisherClient,
    pubsub: Any,
    topic: str,
    server_process: subprocess.Popen,
):
    if pubsub == "google":
        pytest.skip("requires pubsub emulator")

    # override pubsub status
    publisher.update_topic({"name": topic},
                           {"paths": ["status_code=internal"]})
    integration_test.assert_accepted_and_queued()

    # stop server
    server_process.kill()
    server_process.wait()

    # start flush
    process = subprocess.Popen(
        [sys.executable, "-u", "-m", "ingestion_edge.flush"],
        stderr=subprocess.PIPE)
    assert process.stderr is not None
    for line in process.stderr:
        break  # wait for an error to be logged
    assert process.poll() is None  # server still running

    # restore pubsub status
    publisher.update_topic({"name": topic}, {"paths": ["status_code="]})
    try:
        process.wait(5)
    except subprocess.TimeoutExpired:
        # kill after 5 seconds
        process.kill()
    assert process.wait() == 0

    assert json.loads(line)["Fields"]["msg"] == "pubsub unavailable"
    integration_test.assert_delivered()
Пример #18
0
 def _initialize_topic(self):
     try:
         from google.cloud.pubsub_v1 import PublisherClient
     except ImportError:
         raise unittest.SkipTest("Cannot import pubsub")
     self.publisher_client = PublisherClient()
     retry_429(self.publisher_client.create_topic)(self.topic_path)
     policy = self.publisher_client.get_iam_policy(self.topic_path)
     binding = policy.bindings.add()
     binding.role = 'roles/pubsub.publisher'
     binding.members.append(
         'serviceAccount:{}'.format(
             Config.CLIENT.get_service_account_email()))
     self.publisher_client.set_iam_policy(self.topic_path, policy)
Пример #19
0
async def submit(
    request: Request,
    client: PublisherClient,
    q: SQLiteAckQueue,
    topic: str,
    metadata_headers: Dict[str, str],
    timeout: Optional[float],
    **kwargs
) -> response.HTTPResponse:
    """Deliver request to the pubsub topic.

    Deliver to the local queue to be retried on transient errors.
    """
    data = request.body
    attrs = {
        key: value
        for key, value in dict(
            submission_timestamp=datetime.utcnow().isoformat() + "Z",
            uri=request.path,
            protocol="HTTP/" + request.version,
            method=request.method,
            args=request.query_string,
            remote_addr=request.ip,
            host=request.host,
            **{
                attr: request.headers.get(header)
                for header, attr in metadata_headers.items()
            }
        ).items()
        if value is not None
    }
    # assert valid pubsub message
    for value in attrs.values():
        if len(value.encode("utf8")) > 1024:
            # attribute exceeds value size limit of 1024 bytes
            # https://cloud.google.com/pubsub/quotas#resource_limits
            return response.text(
                "header too large\n", HTTP_STATUS.REQUEST_HEADER_FIELDS_TOO_LARGE
            )
    try:
        future = client.publish(topic, data, **attrs)
        await asyncio.wait_for(async_wrap(future), timeout)
    except Exception:
        # api call failure, write to queue
        try:
            q.put((topic, data, attrs))
        except DatabaseError:
            # sqlite queue is probably out of space
            return response.text("", HTTP_STATUS.INSUFFICIENT_STORAGE)
    return response.text("")
Пример #20
0
def init_app(app: Sanic) -> Tuple[PublisherClient, SQLiteAckQueue]:
    """Initialize Sanic app with url rules."""
    # Get PubSub timeout
    timeout = app.config.get("PUBLISH_TIMEOUT_SECONDS")
    # Initialize PubSub client
    client = PublisherClient()
    # Use a SQLiteAckQueue because:
    # * we use acks to ensure messages only removed on success
    # * persist-queue's SQLite*Queue is faster than its Queue
    # * SQLite provides thread-safe and process-safe access
    queue_config = {
        key[6:].lower(): value
        for key, value in app.config.items()
        if key.startswith("QUEUE_")
    }
    q = SQLiteAckQueue(**queue_config)
    # get metadata_headers config
    metadata_headers = app.config["METADATA_HEADERS"]
    # validate attribute keys
    for attribute in metadata_headers.values():
        if len(attribute.encode("utf8")) > 256:
            # https://cloud.google.com/pubsub/quotas#resource_limits
            raise ValueError("Metadata attribute exceeds key size limit of 256 bytes")
    # generate one view_func per topic
    handlers = {
        route.topic: partial(
            submit,
            client=client,
            q=q,
            topic=route.topic,
            metadata_headers=metadata_headers,
            timeout=timeout,
        )
        for route in app.config["ROUTE_TABLE"]
    }
    # add routes for ROUTE_TABLE
    for route in app.config["ROUTE_TABLE"]:
        app.add_route(
            handler=handlers[route.topic],
            uri=route.uri,
            methods=[method.upper() for method in route.methods],
            # required because handler.__name__ does not exist
            # must be a unique name for each handler
            name="submit_" + route.topic,
        )
    return client, q
Пример #21
0
    def publish(
        self,
        topic: str,
        messages: List[Dict],
        project_id: Optional[str] = None,
    ) -> None:
        """
        Publishes messages to a Pub/Sub topic.

        :param topic: the Pub/Sub topic to which to publish; do not
            include the ``projects/{project}/topics/`` prefix.
        :type topic: str
        :param messages: messages to publish; if the data field in a
            message is set, it should be a bytestring (utf-8 encoded)
        :type messages: list of PubSub messages; see
            http://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
        :param project_id: Optional, the GCP project ID in which to publish.
            If set to None or missing, the default project_id from the GCP connection is used.
        :type project_id: str
        """
        if not project_id:
            raise ValueError("Project ID should be set.")
        self._validate_messages(messages)

        publisher = self.get_conn()
        topic_path = PublisherClient.topic_path(project_id, topic)  # pylint: disable=no-member

        self.log.info("Publish %d messages to topic (path) %s", len(messages),
                      topic_path)
        try:
            for message in messages:
                publisher.publish(topic=topic_path,
                                  data=message.get("data", b''),
                                  **message.get('attributes', {}))
        except GoogleAPICallError as e:
            raise PubSubException(
                'Error publishing to topic {}'.format(topic_path), e)

        self.log.info("Published %d messages to topic (path) %s",
                      len(messages), topic_path)
Пример #22
0
def emulator(node_pools: List[str], options: Dict[str, Any]) -> Iterator[str]:
    if "emulator" in node_pools:
        kube_apply("kube/emulator.deploy.yml", **options)
        kube_apply("kube/emulator.svc.yml")
        yield "emulator:8000"
    elif "server" in node_pools:
        pubsub = PublisherClient()
        topic = f"projects/{options['project']}/topics/{options['topic']}"
        try:
            pubsub.create_topic(topic)
        except AlreadyExists:
            pass
        yield ""
        pubsub.delete_topic(topic)
    else:
        yield ""
Пример #23
0
def ensure_topic_and_subscription():
    from google.cloud.pubsub_v1 import PublisherClient, SubscriberClient
    publisher_client = PublisherClient()
    try:
        publisher_client.create_topic(
            publisher_client.topic_path(PROJECT_ID, TOPIC_NAME))
    except AlreadyExists:
        pass

    subscriber_client = SubscriberClient()
    try:
        subscriber_client.create_subscription(
            subscriber_client.subscription_path(PROJECT_ID, SUBSCRIPTION_NAME),
            publisher_client.topic_path(PROJECT_ID, TOPIC_NAME))
    except AlreadyExists:
        pass
Пример #24
0
class EmsPublisherClient:
    def __init__(self):
        self.__client = PublisherClient()

    def publish(self, topic: str, data: bytes, **attrs) -> Future:
        return self.__client.publish(topic=topic, data=data, **attrs)

    def topic_create_if_not_exists(self, project_id: str, topic_name: str):
        topic_path = self.__client.api.topic_path(project_id, topic_name)
        try:
            self.__client.api.create_topic(topic_path)
            LOGGER.info("Topic %s created in project %s", topic_name,
                        project_id)
        except AlreadyExists:
            LOGGER.info("Topic %s already exists in project %s", topic_name,
                        project_id)

    def delete_topic_if_exists(self, project_id: str, topic_name: str):
        topic_path = self.__client.api.topic_path(project_id, topic_name)
        try:
            self.__client.api.delete_topic(topic_path)
            LOGGER.info("Topic %s deleted in project %s", topic_name,
                        project_id)
        except NotFound:
            LOGGER.info("Topic %s not found in project %s", topic_name,
                        project_id)

    @staticmethod
    def subscription_create(project_id: str, topic_name: str,
                            subscription_name: str):
        subscriber = SubscriberClient()

        topic_path = subscriber.api.topic_path(project_id, topic_name)
        subscription_path = subscriber.api.subscription_path(
            project_id, subscription_name)

        subscriber.api.create_subscription(subscription_path, topic_path)
Пример #25
0
def setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool:
    """Activate Google Pub/Sub component."""
    config = yaml_config[DOMAIN]
    project_id = config[CONF_PROJECT_ID]
    topic_name = config[CONF_TOPIC_NAME]
    service_principal_path = hass.config.path(config[CONF_SERVICE_PRINCIPAL])

    if not os.path.isfile(service_principal_path):
        _LOGGER.error("Path to credentials file cannot be found")
        return False

    entities_filter = config[CONF_FILTER]

    publisher = PublisherClient.from_service_account_json(
        service_principal_path)

    topic_path = publisher.topic_path(  # pylint: disable=no-member
        project_id, topic_name)

    encoder = DateTimeJSONEncoder()

    def send_to_pubsub(event: Event):
        """Send states to Pub/Sub."""
        state = event.data.get("new_state")
        if (state is None
                or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE)
                or not entities_filter(state.entity_id)):
            return

        as_dict = state.as_dict()
        data = json.dumps(obj=as_dict, default=encoder.encode).encode("utf-8")

        publisher.publish(topic_path, data=data)

    hass.bus.listen(EVENT_STATE_CHANGED, send_to_pubsub)

    return True
 def setUpClass(cls):
     cls.publisher = PublisherClient()
Пример #27
0
    def create_topic(
        self,
        topic: str,
        project_id: str,
        fail_if_exists: bool = False,
        labels: Optional[Dict[str, str]] = None,
        message_storage_policy: Union[Dict, MessageStoragePolicy] = None,
        kms_key_name: Optional[str] = None,
        retry: Optional[Retry] = None,
        timeout: Optional[float] = None,
        metadata: Optional[Sequence[Tuple[str, str]]] = None,
    ) -> None:
        """
        Creates a Pub/Sub topic, if it does not already exist.

        :param topic: the Pub/Sub topic name to create; do not
            include the ``projects/{project}/topics/`` prefix.
        :type topic: str
        :param project_id: Optional, the Google Cloud project ID in which to create the topic
            If set to None or missing, the default project_id from the Google Cloud connection is used.
        :type project_id: str
        :param fail_if_exists: if set, raise an exception if the topic
            already exists
        :type fail_if_exists: bool
        :param labels: Client-assigned labels; see
            https://cloud.google.com/pubsub/docs/labels
        :type labels: Dict[str, str]
        :param message_storage_policy: Policy constraining the set
            of Google Cloud regions where messages published to
            the topic may be stored. If not present, then no constraints
            are in effect.
        :type message_storage_policy:
            Union[Dict, google.cloud.pubsub_v1.types.MessageStoragePolicy]
        :param kms_key_name: The resource name of the Cloud KMS CryptoKey
            to be used to protect access to messages published on this topic.
            The expected format is
            ``projects/*/locations/*/keyRings/*/cryptoKeys/*``.
        :type kms_key_name: str
        :param retry: (Optional) A retry object used to retry requests.
            If None is specified, requests will not be retried.
        :type retry: google.api_core.retry.Retry
        :param timeout: (Optional) The amount of time, in seconds, to wait for the request
            to complete. Note that if retry is specified, the timeout applies to each
            individual attempt.
        :type timeout: float
        :param metadata: (Optional) Additional metadata that is provided to the method.
        :type metadata: Sequence[Tuple[str, str]]]
        """
        publisher = self.get_conn()
        topic_path = PublisherClient.topic_path(project_id, topic)  # pylint: disable=no-member

        # Add airflow-version label to the topic
        labels = labels or {}
        labels['airflow-version'] = 'v' + version.replace('.', '-').replace('+', '-')

        self.log.info("Creating topic (path) %s", topic_path)
        try:
            # pylint: disable=no-member
            publisher.create_topic(
                name=topic_path,
                labels=labels,
                message_storage_policy=message_storage_policy,
                kms_key_name=kms_key_name,
                retry=retry,
                timeout=timeout,
                metadata=metadata,
            )
        except AlreadyExists:
            self.log.warning('Topic already exists: %s', topic)
            if fail_if_exists:
                raise PubSubException('Topic already exists: {}'.format(topic))
        except GoogleAPICallError as e:
            raise PubSubException('Error creating topic {}'.format(topic), e)

        self.log.info("Created topic (path) %s", topic_path)
Пример #28
0
class TestStorageNotificationCRUD(unittest.TestCase):

    topic = None
    TOPIC_NAME = 'notification' + unique_resource_id('-')
    CUSTOM_ATTRIBUTES = {
        'attr1': 'value1',
        'attr2': 'value2',
    }
    BLOB_NAME_PREFIX = 'blob-name-prefix/'

    @property
    def topic_path(self):
        return 'projects/{}/topics/{}'.format(Config.CLIENT.project,
                                              self.TOPIC_NAME)

    def _intialize_topic(self):
        try:
            from google.cloud.pubsub_v1 import PublisherClient
        except ImportError:
            raise unittest.SkipTest("Cannot import pubsub")
        self.publisher_client = PublisherClient()
        retry_429(self.publisher_client.create_topic)(self.topic_path)
        policy = self.publisher_client.get_iam_policy(self.topic_path)
        binding = policy.bindings.add()
        binding.role = 'roles/pubsub.publisher'
        binding.members.append(
            'serviceAccount:{}'
            '@gs-project-accounts.iam.gserviceaccount.com'.format(
                Config.CLIENT.project))
        self.publisher_client.set_iam_policy(self.topic_path, policy)

    def setUp(self):
        self.case_buckets_to_delete = []
        self._intialize_topic()

    def tearDown(self):
        retry_429(self.publisher_client.delete_topic)(self.topic_path)
        with Config.CLIENT.batch():
            for bucket_name in self.case_buckets_to_delete:
                bucket = Config.CLIENT.bucket(bucket_name)
                retry_429(bucket.delete)()

    @staticmethod
    def event_types():
        from google.cloud.storage.notification import (
            OBJECT_FINALIZE_EVENT_TYPE, OBJECT_DELETE_EVENT_TYPE)

        return [OBJECT_FINALIZE_EVENT_TYPE, OBJECT_DELETE_EVENT_TYPE]

    @staticmethod
    def payload_format():
        from google.cloud.storage.notification import (
            JSON_API_V1_PAYLOAD_FORMAT)

        return JSON_API_V1_PAYLOAD_FORMAT

    def test_notification_minimal(self):
        new_bucket_name = 'notification-minimal' + unique_resource_id('-')
        bucket = retry_429(Config.CLIENT.create_bucket)(new_bucket_name)
        self.case_buckets_to_delete.append(new_bucket_name)
        self.assertEqual(list(bucket.list_notifications()), [])
        notification = bucket.notification(self.TOPIC_NAME)
        retry_429(notification.create)()
        try:
            self.assertTrue(notification.exists())
            self.assertIsNotNone(notification.notification_id)
            notifications = list(bucket.list_notifications())
            self.assertEqual(len(notifications), 1)
            self.assertEqual(notifications[0].topic_name, self.TOPIC_NAME)
        finally:
            notification.delete()

    def test_notification_explicit(self):
        new_bucket_name = 'notification-explicit' + unique_resource_id('-')
        bucket = retry_429(Config.CLIENT.create_bucket)(new_bucket_name)
        self.case_buckets_to_delete.append(new_bucket_name)
        notification = bucket.notification(
            self.TOPIC_NAME,
            custom_attributes=self.CUSTOM_ATTRIBUTES,
            event_types=self.event_types(),
            blob_name_prefix=self.BLOB_NAME_PREFIX,
            payload_format=self.payload_format(),
        )
        retry_429(notification.create)()
        try:
            self.assertTrue(notification.exists())
            self.assertIsNotNone(notification.notification_id)
            self.assertEqual(notification.custom_attributes,
                             self.CUSTOM_ATTRIBUTES)
            self.assertEqual(notification.event_types, self.event_types())
            self.assertEqual(notification.blob_name_prefix,
                             self.BLOB_NAME_PREFIX)
            self.assertEqual(notification.payload_format,
                             self.payload_format())
        finally:
            notification.delete()

    @unittest.skipUnless(USER_PROJECT, 'USER_PROJECT not set in environment.')
    def test_notification_w_user_project(self):
        new_bucket_name = 'notification-minimal' + unique_resource_id('-')
        bucket = retry_429(Config.CLIENT.create_bucket)(new_bucket_name,
                                                        requester_pays=True)
        self.case_buckets_to_delete.append(new_bucket_name)
        with_user_project = Config.CLIENT.bucket(new_bucket_name,
                                                 user_project=USER_PROJECT)
        self.assertEqual(list(with_user_project.list_notifications()), [])
        notification = with_user_project.notification(self.TOPIC_NAME)
        retry_429(notification.create)()
        try:
            self.assertTrue(notification.exists())
            self.assertIsNotNone(notification.notification_id)
            notifications = list(with_user_project.list_notifications())
            self.assertEqual(len(notifications), 1)
            self.assertEqual(notifications[0].topic_name, self.TOPIC_NAME)
        finally:
            notification.delete()
Пример #29
0
def client() -> PublisherClient:
    if "PUBSUB_EMULATOR_HOST" not in os.environ:
        return PublisherClient(channel=grpc.insecure_channel(target=""))
    else:
        return PublisherClient()
def pubsub_topic(pubsub_client: pubsub_v1.PublisherClient, project_id):
    topic_id = resource_prefix()
    topic_path = pubsub_v1.PublisherClient.topic_path(project_id, topic_id)
    pubsub_client.create_topic(name=topic_path)
    yield topic_path
    pubsub_client.delete_topic(topic=topic_path)
 def setUpClass(cls):
     cls.publisher = PublisherClient()
     cls.subscriber = SubscriberClient()
Пример #32
0
class TestStorageNotificationCRUD(unittest.TestCase):

    topic = None
    TOPIC_NAME = 'notification' + unique_resource_id('-')
    CUSTOM_ATTRIBUTES = {
        'attr1': 'value1',
        'attr2': 'value2',
    }
    BLOB_NAME_PREFIX = 'blob-name-prefix/'

    @property
    def topic_path(self):
        return 'projects/{}/topics/{}'.format(
            Config.CLIENT.project, self.TOPIC_NAME)

    def _initialize_topic(self):
        try:
            from google.cloud.pubsub_v1 import PublisherClient
        except ImportError:
            raise unittest.SkipTest("Cannot import pubsub")
        self.publisher_client = PublisherClient()
        retry_429(self.publisher_client.create_topic)(self.topic_path)
        policy = self.publisher_client.get_iam_policy(self.topic_path)
        binding = policy.bindings.add()
        binding.role = 'roles/pubsub.publisher'
        binding.members.append(
            'serviceAccount:{}'.format(
                Config.CLIENT.get_service_account_email()))
        self.publisher_client.set_iam_policy(self.topic_path, policy)

    def setUp(self):
        self.case_buckets_to_delete = []
        self._initialize_topic()

    def tearDown(self):
        retry_429(self.publisher_client.delete_topic)(self.topic_path)
        with Config.CLIENT.batch():
            for bucket_name in self.case_buckets_to_delete:
                bucket = Config.CLIENT.bucket(bucket_name)
                retry_429(bucket.delete)()

    @staticmethod
    def event_types():
        from google.cloud.storage.notification import (
            OBJECT_FINALIZE_EVENT_TYPE,
            OBJECT_DELETE_EVENT_TYPE)

        return [OBJECT_FINALIZE_EVENT_TYPE, OBJECT_DELETE_EVENT_TYPE]

    @staticmethod
    def payload_format():
        from google.cloud.storage.notification import (
            JSON_API_V1_PAYLOAD_FORMAT)

        return JSON_API_V1_PAYLOAD_FORMAT

    def test_notification_minimal(self):
        new_bucket_name = 'notification-minimal' + unique_resource_id('-')
        bucket = retry_429(Config.CLIENT.create_bucket)(new_bucket_name)
        self.case_buckets_to_delete.append(new_bucket_name)
        self.assertEqual(list(bucket.list_notifications()), [])
        notification = bucket.notification(self.TOPIC_NAME)
        retry_429_503(notification.create)()
        try:
            self.assertTrue(notification.exists())
            self.assertIsNotNone(notification.notification_id)
            notifications = list(bucket.list_notifications())
            self.assertEqual(len(notifications), 1)
            self.assertEqual(notifications[0].topic_name, self.TOPIC_NAME)
        finally:
            notification.delete()

    def test_notification_explicit(self):
        new_bucket_name = 'notification-explicit' + unique_resource_id('-')
        bucket = retry_429(Config.CLIENT.create_bucket)(new_bucket_name)
        self.case_buckets_to_delete.append(new_bucket_name)
        notification = bucket.notification(
            self.TOPIC_NAME,
            custom_attributes=self.CUSTOM_ATTRIBUTES,
            event_types=self.event_types(),
            blob_name_prefix=self.BLOB_NAME_PREFIX,
            payload_format=self.payload_format(),
        )
        retry_429_503(notification.create)()
        try:
            self.assertTrue(notification.exists())
            self.assertIsNotNone(notification.notification_id)
            self.assertEqual(
                notification.custom_attributes, self.CUSTOM_ATTRIBUTES)
            self.assertEqual(notification.event_types, self.event_types())
            self.assertEqual(
                notification.blob_name_prefix, self.BLOB_NAME_PREFIX)
            self.assertEqual(
                notification.payload_format, self.payload_format())
        finally:
            notification.delete()

    @unittest.skipUnless(USER_PROJECT, 'USER_PROJECT not set in environment.')
    def test_notification_w_user_project(self):
        new_bucket_name = 'notification-minimal' + unique_resource_id('-')
        retry_429(Config.CLIENT.create_bucket)(
            new_bucket_name, requester_pays=True)
        self.case_buckets_to_delete.append(new_bucket_name)
        with_user_project = Config.CLIENT.bucket(
            new_bucket_name, user_project=USER_PROJECT)
        self.assertEqual(list(with_user_project.list_notifications()), [])
        notification = with_user_project.notification(self.TOPIC_NAME)
        retry_429(notification.create)()
        try:
            self.assertTrue(notification.exists())
            self.assertIsNotNone(notification.notification_id)
            notifications = list(with_user_project.list_notifications())
            self.assertEqual(len(notifications), 1)
            self.assertEqual(notifications[0].topic_name, self.TOPIC_NAME)
        finally:
            notification.delete()