[Notes] [Git][BuildGrid/buildgrid][mablanch/144-jwt-authentication] 10 commits: cmd_bot.py: Port to new client channel helpers



Title: GitLab

Martin Blanchard pushed to branch mablanch/144-jwt-authentication at BuildGrid / buildgrid

Commits:

23 changed files:

Changes:

  • .gitlab-ci.yml
    ... ... @@ -20,6 +20,7 @@ before_script:
    20 20
       variables:
    
    21 21
         PYTEST_ADDOPTS: "--color=yes"
    
    22 22
       script:
    
    23
    +    - python3 -m pip install --user --editable ".[auth]"
    
    23 24
         - python3 setup.py test
    
    24 25
         - mkdir -p coverage/
    
    25 26
         - cp .coverage coverage/coverage."${CI_JOB_NAME}"
    

  • buildgrid/_app/commands/cmd_bot.py
    ... ... @@ -22,16 +22,15 @@ Create a bot interface and request work
    22 22
     
    
    23 23
     from pathlib import Path, PurePath
    
    24 24
     import sys
    
    25
    -from urllib.parse import urlparse
    
    26 25
     
    
    27 26
     import click
    
    28
    -import grpc
    
    29 27
     
    
    30 28
     from buildgrid.bot import bot, interface, session
    
    31 29
     from buildgrid.bot.hardware.interface import HardwareInterface
    
    32 30
     from buildgrid.bot.hardware.device import Device
    
    33 31
     from buildgrid.bot.hardware.worker import Worker
    
    34
    -
    
    32
    +from buildgrid.client.authentication import setup_channel
    
    33
    +from buildgrid._exceptions import InvalidArgumentError
    
    35 34
     
    
    36 35
     from ..bots import buildbox, dummy, host
    
    37 36
     from ..cli import pass_context, setup_logging
    
    ... ... @@ -40,20 +39,22 @@ from ..cli import pass_context, setup_logging
    40 39
     @click.group(name='bot', short_help="Create and register bot clients.")
    
    41 40
     @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
    
    42 41
                   help="Remote execution server's URL (port defaults to 50051 if not specified).")
    
    42
    +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
    
    43
    +              help="Authorization token for the remote.")
    
    43 44
     @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
    
    44
    -              help="Private client key for TLS (PEM-encoded)")
    
    45
    +              help="Private client key for TLS (PEM-encoded).")
    
    45 46
     @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    46
    -              help="Public client certificate for TLS (PEM-encoded)")
    
    47
    +              help="Public client certificate for TLS (PEM-encoded).")
    
    47 48
     @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    48
    -              help="Public server certificate for TLS (PEM-encoded)")
    
    49
    +              help="Public server certificate for TLS (PEM-encoded).")
    
    49 50
     @click.option('--remote-cas', type=click.STRING, default=None, show_default=True,
    
    50 51
                   help="Remote CAS server's URL (port defaults to 11001 if not specified).")
    
    51 52
     @click.option('--cas-client-key', type=click.Path(exists=True, dir_okay=False), default=None,
    
    52
    -              help="Private CAS client key for TLS (PEM-encoded)")
    
    53
    +              help="Private CAS client key for TLS (PEM-encoded).")
    
    53 54
     @click.option('--cas-client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    54
    -              help="Public CAS client certificate for TLS (PEM-encoded)")
    
    55
    +              help="Public CAS client certificate for TLS (PEM-encoded).")
    
    55 56
     @click.option('--cas-server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    56
    -              help="Public CAS server certificate for TLS (PEM-encoded)")
    
    57
    +              help="Public CAS server certificate for TLS (PEM-encoded).")
    
    57 58
     @click.option('--update-period', type=click.FLOAT, default=0.5, show_default=True,
    
    58 59
                   help="Time period for bot updates to the server in seconds.")
    
    59 60
     @click.option('--parent', type=click.STRING, default='main', show_default=True,
    
    ... ... @@ -61,69 +62,31 @@ from ..cli import pass_context, setup_logging
    61 62
     @click.option('-v', '--verbose', count=True,
    
    62 63
                   help='Increase log verbosity level.')
    
    63 64
     @pass_context
    
    64
    -def cli(context, parent, update_period, remote, client_key, client_cert, server_cert,
    
    65
    +def cli(context, parent, update_period, remote, auth_token, client_key, client_cert, server_cert,
    
    65 66
             remote_cas, cas_client_key, cas_client_cert, cas_server_cert, verbose):
    
    66 67
         setup_logging(verbosity=verbose)
    
    67 68
         # Setup the remote execution server channel:
    
    68
    -    url = urlparse(remote)
    
    69
    -
    
    70
    -    context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
    
    71
    -    context.remote_url = remote
    
    72
    -    context.update_period = update_period
    
    73
    -    context.parent = parent
    
    74
    -
    
    75
    -    if url.scheme == 'http':
    
    76
    -        context.channel = grpc.insecure_channel(context.remote)
    
    77
    -
    
    78
    -        context.client_key = None
    
    79
    -        context.client_cert = None
    
    80
    -        context.server_cert = None
    
    81
    -    else:
    
    82
    -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
    
    83
    -        if not credentials:
    
    84
    -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
    
    85
    -            sys.exit(-1)
    
    86
    -
    
    87
    -        context.channel = grpc.secure_channel(context.remote, credentials)
    
    88
    -
    
    89
    -        context.client_key = credentials.client_key
    
    90
    -        context.client_cert = credentials.client_cert
    
    91
    -        context.server_cert = credentials.server_cert
    
    92
    -
    
    93
    -    # Setup the remote CAS server channel, if separated:
    
    94
    -    if remote_cas is not None and remote_cas != remote:
    
    95
    -        cas_url = urlparse(remote_cas)
    
    96
    -
    
    97
    -        context.remote_cas = '{}:{}'.format(cas_url.hostname, cas_url.port or 11001)
    
    98
    -        context.remote_cas_url = remote_cas
    
    69
    +    try:
    
    70
    +        context.channel, details = setup_channel(remote, auth_token=auth_token, server_cert=server_cert,
    
    71
    +                                                 client_key=client_key, client_cert=client_cert)
    
    99 72
     
    
    100
    -        if cas_url.scheme == 'http':
    
    101
    -            context.cas_channel = grpc.insecure_channel(context.remote_cas)
    
    73
    +        if remote_cas is not None and remote_cas != remote:
    
    74
    +            context.cas_channel, details = setup_channel(remote_cas, server_cert=cas_server_cert,
    
    75
    +                                                         client_key=cas_client_key, client_cert=cas_client_cert)
    
    76
    +            context.remote_cas_url = remote_cas
    
    102 77
     
    
    103
    -            context.cas_client_key = None
    
    104
    -            context.cas_client_cert = None
    
    105
    -            context.cas_server_cert = None
    
    106 78
             else:
    
    107
    -            cas_credentials = context.load_client_credentials(cas_client_key, cas_client_cert, cas_server_cert)
    
    108
    -            if not cas_credentials:
    
    109
    -                click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
    
    110
    -                sys.exit(-1)
    
    111
    -
    
    112
    -            context.cas_channel = grpc.secure_channel(context.remote_cas, cas_credentials)
    
    79
    +            context.cas_channel = context.channel
    
    80
    +            context.remote_cas_url = remote
    
    113 81
     
    
    114
    -            context.cas_client_key = cas_credentials.client_key
    
    115
    -            context.cas_client_cert = cas_credentials.client_cert
    
    116
    -            context.cas_server_cert = cas_credentials.server_cert
    
    82
    +        context.cas_client_key, context.cas_client_cert, context.cas_server_cert = details
    
    117 83
     
    
    118
    -    else:
    
    119
    -        context.remote_cas = context.remote
    
    120
    -        context.remote_cas_url = remote
    
    84
    +    except InvalidArgumentError as e:
    
    85
    +        click.echo("Error: {}.".format(e), err=True)
    
    86
    +        sys.exit(-1)
    
    121 87
     
    
    122
    -        context.cas_channel = context.channel
    
    123
    -
    
    124
    -        context.cas_client_key = context.client_key
    
    125
    -        context.cas_client_cert = context.client_cert
    
    126
    -        context.cas_server_cert = context.server_cert
    
    88
    +    context.update_period = update_period
    
    89
    +    context.parent = parent
    
    127 90
     
    
    128 91
         bot_interface = interface.BotInterface(context.channel)
    
    129 92
     
    

  • buildgrid/_app/commands/cmd_capabilities.py
    ... ... @@ -13,13 +13,13 @@
    13 13
     # limitations under the License.
    
    14 14
     
    
    15 15
     
    
    16
    -import sys
    
    17
    -from urllib.parse import urlparse
    
    18
    -
    
    19 16
     import click
    
    20
    -import grpc
    
    17
    +from google.protobuf import json_format
    
    18
    +import sys
    
    21 19
     
    
    20
    +from buildgrid.client.authentication import setup_channel
    
    22 21
     from buildgrid.client.capabilities import CapabilitiesInterface
    
    22
    +from buildgrid._exceptions import InvalidArgumentError
    
    23 23
     
    
    24 24
     from ..cli import pass_context
    
    25 25
     
    
    ... ... @@ -27,32 +27,30 @@ from ..cli import pass_context
    27 27
     @click.command(name='capabilities', short_help="Capabilities service.")
    
    28 28
     @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
    
    29 29
                   help="Remote execution server's URL (port defaults to 50051 if no specified).")
    
    30
    +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
    
    31
    +              help="Authorization token for the remote.")
    
    30 32
     @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
    
    31
    -              help="Private client key for TLS (PEM-encoded)")
    
    33
    +              help="Private client key for TLS (PEM-encoded).")
    
    32 34
     @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    33
    -              help="Public client certificate for TLS (PEM-encoded)")
    
    35
    +              help="Public client certificate for TLS (PEM-encoded).")
    
    34 36
     @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    35
    -              help="Public server certificate for TLS (PEM-encoded)")
    
    37
    +              help="Public server certificate for TLS (PEM-encoded).")
    
    36 38
     @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
    
    37 39
                   help="Targeted farm instance name.")
    
    38 40
     @pass_context
    
    39
    -def cli(context, remote, instance_name, client_key, client_cert, server_cert):
    
    40
    -    click.echo("Getting capabilities...")
    
    41
    -    url = urlparse(remote)
    
    42
    -
    
    43
    -    remote = '{}:{}'.format(url.hostname, url.port or 50051)
    
    44
    -    instance_name = instance_name
    
    45
    -
    
    46
    -    if url.scheme == 'http':
    
    47
    -        channel = grpc.insecure_channel(remote)
    
    48
    -    else:
    
    49
    -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
    
    50
    -        if not credentials:
    
    51
    -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
    
    52
    -            sys.exit(-1)
    
    53
    -
    
    54
    -        channel = grpc.secure_channel(remote, credentials)
    
    55
    -
    
    56
    -    interface = CapabilitiesInterface(channel)
    
    57
    -    response = interface.get_capabilities(instance_name)
    
    58
    -    click.echo(response)
    41
    +def cli(context, remote, instance_name, auth_token, client_key, client_cert, server_cert):
    
    42
    +    """Entry point for the bgd-capabilities CLI command group."""
    
    43
    +    try:
    
    44
    +        context.channel, _ = setup_channel(remote, auth_token=auth_token,
    
    45
    +                                           client_key=client_key, client_cert=client_cert)
    
    46
    +
    
    47
    +    except InvalidArgumentError as e:
    
    48
    +        click.echo("Error: {}.".format(e), err=True)
    
    49
    +        sys.exit(-1)
    
    50
    +
    
    51
    +    context.instance_name = instance_name
    
    52
    +
    
    53
    +    interface = CapabilitiesInterface(context.channel)
    
    54
    +    response = interface.get_capabilities(context.instance_name)
    
    55
    +
    
    56
    +    click.echo(json_format.MessageToJson(response))

  • buildgrid/_app/commands/cmd_cas.py
    ... ... @@ -22,12 +22,12 @@ Request work to be executed and monitor status of jobs.
    22 22
     
    
    23 23
     import os
    
    24 24
     import sys
    
    25
    -from urllib.parse import urlparse
    
    26 25
     
    
    27 26
     import click
    
    28
    -import grpc
    
    29 27
     
    
    28
    +from buildgrid.client.authentication import setup_channel
    
    30 29
     from buildgrid.client.cas import download, upload
    
    30
    +from buildgrid._exceptions import InvalidArgumentError
    
    31 31
     from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
    
    32 32
     from buildgrid.utils import create_digest, merkle_tree_maker, read_file
    
    33 33
     
    
    ... ... @@ -37,32 +37,28 @@ from ..cli import pass_context
    37 37
     @click.group(name='cas', short_help="Interact with the CAS server.")
    
    38 38
     @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
    
    39 39
                   help="Remote execution server's URL (port defaults to 50051 if no specified).")
    
    40
    +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
    
    41
    +              help="Authorization token for the remote.")
    
    40 42
     @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
    
    41
    -              help="Private client key for TLS (PEM-encoded)")
    
    43
    +              help="Private client key for TLS (PEM-encoded).")
    
    42 44
     @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    43
    -              help="Public client certificate for TLS (PEM-encoded)")
    
    45
    +              help="Public client certificate for TLS (PEM-encoded).")
    
    44 46
     @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    45 47
                   help="Public server certificate for TLS (PEM-encoded)")
    
    46 48
     @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
    
    47 49
                   help="Targeted farm instance name.")
    
    48 50
     @pass_context
    
    49
    -def cli(context, remote, instance_name, client_key, client_cert, server_cert):
    
    50
    -    url = urlparse(remote)
    
    51
    +def cli(context, remote, instance_name, auth_token, client_key, client_cert, server_cert):
    
    52
    +    """Entry point for the bgd-cas CLI command group."""
    
    53
    +    try:
    
    54
    +        context.channel, _ = setup_channel(remote, auth_token=auth_token,
    
    55
    +                                           client_key=client_key, client_cert=client_cert)
    
    51 56
     
    
    52
    -    context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
    
    53
    -    context.instance_name = instance_name
    
    54
    -
    
    55
    -    if url.scheme == 'http':
    
    56
    -        context.channel = grpc.insecure_channel(context.remote)
    
    57
    -    else:
    
    58
    -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
    
    59
    -        if not credentials:
    
    60
    -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
    
    61
    -            sys.exit(-1)
    
    57
    +    except InvalidArgumentError as e:
    
    58
    +        click.echo("Error: {}.".format(e), err=True)
    
    59
    +        sys.exit(-1)
    
    62 60
     
    
    63
    -        context.channel = grpc.secure_channel(context.remote, credentials)
    
    64
    -
    
    65
    -    click.echo("Starting for remote=[{}]".format(context.remote))
    
    61
    +    context.instance_name = instance_name
    
    66 62
     
    
    67 63
     
    
    68 64
     @cli.command('upload-dummy', short_help="Upload a dummy action. Should be used with `execute dummy-request`")
    

  • buildgrid/_app/commands/cmd_execute.py
    ... ... @@ -23,12 +23,12 @@ Request work to be executed and monitor status of jobs.
    23 23
     import os
    
    24 24
     import stat
    
    25 25
     import sys
    
    26
    -from urllib.parse import urlparse
    
    27 26
     
    
    28 27
     import click
    
    29
    -import grpc
    
    30 28
     
    
    29
    +from buildgrid.client.authentication import setup_channel
    
    31 30
     from buildgrid.client.cas import download, upload
    
    31
    +from buildgrid._exceptions import InvalidArgumentError
    
    32 32
     from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
    
    33 33
     from buildgrid.utils import create_digest
    
    34 34
     
    
    ... ... @@ -38,32 +38,28 @@ from ..cli import pass_context
    38 38
     @click.group(name='execute', short_help="Execute simple operations.")
    
    39 39
     @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
    
    40 40
                   help="Remote execution server's URL (port defaults to 50051 if no specified).")
    
    41
    +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
    
    42
    +              help="Authorization token for the remote.")
    
    41 43
     @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
    
    42
    -              help="Private client key for TLS (PEM-encoded)")
    
    44
    +              help="Private client key for TLS (PEM-encoded).")
    
    43 45
     @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    44
    -              help="Public client certificate for TLS (PEM-encoded)")
    
    46
    +              help="Public client certificate for TLS (PEM-encoded).")
    
    45 47
     @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    46
    -              help="Public server certificate for TLS (PEM-encoded)")
    
    48
    +              help="Public server certificate for TLS (PEM-encoded).")
    
    47 49
     @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
    
    48 50
                   help="Targeted farm instance name.")
    
    49 51
     @pass_context
    
    50
    -def cli(context, remote, instance_name, client_key, client_cert, server_cert):
    
    51
    -    url = urlparse(remote)
    
    52
    +def cli(context, remote, instance_name, auth_token, client_key, client_cert, server_cert):
    
    53
    +    """Entry point for the bgd-execute CLI command group."""
    
    54
    +    try:
    
    55
    +        context.channel, _ = setup_channel(remote, auth_token=auth_token,
    
    56
    +                                           client_key=client_key, client_cert=client_cert)
    
    52 57
     
    
    53
    -    context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
    
    54
    -    context.instance_name = instance_name
    
    55
    -
    
    56
    -    if url.scheme == 'http':
    
    57
    -        context.channel = grpc.insecure_channel(context.remote)
    
    58
    -    else:
    
    59
    -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
    
    60
    -        if not credentials:
    
    61
    -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
    
    62
    -            sys.exit(-1)
    
    58
    +    except InvalidArgumentError as e:
    
    59
    +        click.echo("Error: {}.".format(e), err=True)
    
    60
    +        sys.exit(-1)
    
    63 61
     
    
    64
    -        context.channel = grpc.secure_channel(context.remote, credentials)
    
    65
    -
    
    66
    -    click.echo("Starting for remote=[{}]".format(context.remote))
    
    62
    +    context.instance_name = instance_name
    
    67 63
     
    
    68 64
     
    
    69 65
     @cli.command('request-dummy', short_help="Send a dummy action.")
    

  • buildgrid/_app/commands/cmd_operation.py
    ... ... @@ -22,15 +22,15 @@ Check the status of operations
    22 22
     
    
    23 23
     from collections import OrderedDict
    
    24 24
     from operator import attrgetter
    
    25
    -from urllib.parse import urlparse
    
    26 25
     import sys
    
    27 26
     from textwrap import indent
    
    28 27
     
    
    29 28
     import click
    
    30 29
     from google.protobuf import json_format
    
    31
    -import grpc
    
    32 30
     
    
    31
    +from buildgrid.client.authentication import setup_channel
    
    33 32
     from buildgrid._enums import OperationStage
    
    33
    +from buildgrid._exceptions import InvalidArgumentError
    
    34 34
     from buildgrid._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
    
    35 35
     from buildgrid._protos.google.longrunning import operations_pb2, operations_pb2_grpc
    
    36 36
     from buildgrid._protos.google.rpc import code_pb2
    
    ... ... @@ -41,32 +41,28 @@ from ..cli import pass_context
    41 41
     @click.group(name='operation', short_help="Long running operations commands.")
    
    42 42
     @click.option('--remote', type=click.STRING, default='http://localhost:50051', show_default=True,
    
    43 43
                   help="Remote execution server's URL (port defaults to 50051 if no specified).")
    
    44
    +@click.option('--auth-token', type=click.Path(exists=True, dir_okay=False), default=None,
    
    45
    +              help="Authorization token for the remote.")
    
    44 46
     @click.option('--client-key', type=click.Path(exists=True, dir_okay=False), default=None,
    
    45
    -              help="Private client key for TLS (PEM-encoded)")
    
    47
    +              help="Private client key for TLS (PEM-encoded).")
    
    46 48
     @click.option('--client-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    47
    -              help="Public client certificate for TLS (PEM-encoded)")
    
    49
    +              help="Public client certificate for TLS (PEM-encoded).")
    
    48 50
     @click.option('--server-cert', type=click.Path(exists=True, dir_okay=False), default=None,
    
    49
    -              help="Public server certificate for TLS (PEM-encoded)")
    
    51
    +              help="Public server certificate for TLS (PEM-encoded).")
    
    50 52
     @click.option('--instance-name', type=click.STRING, default='main', show_default=True,
    
    51 53
                   help="Targeted farm instance name.")
    
    52 54
     @pass_context
    
    53
    -def cli(context, remote, instance_name, client_key, client_cert, server_cert):
    
    54
    -    url = urlparse(remote)
    
    55
    +def cli(context, remote, instance_name, auth_token, client_key, client_cert, server_cert):
    
    56
    +    """Entry point for the bgd-operation CLI command group."""
    
    57
    +    try:
    
    58
    +        context.channel, _ = setup_channel(remote, auth_token=auth_token,
    
    59
    +                                           client_key=client_key, client_cert=client_cert)
    
    55 60
     
    
    56
    -    context.remote = '{}:{}'.format(url.hostname, url.port or 50051)
    
    57
    -    context.instance_name = instance_name
    
    58
    -
    
    59
    -    if url.scheme == 'http':
    
    60
    -        context.channel = grpc.insecure_channel(context.remote)
    
    61
    -    else:
    
    62
    -        credentials = context.load_client_credentials(client_key, client_cert, server_cert)
    
    63
    -        if not credentials:
    
    64
    -            click.echo("ERROR: no TLS keys were specified and no defaults could be found.", err=True)
    
    65
    -            sys.exit(-1)
    
    61
    +    except InvalidArgumentError as e:
    
    62
    +        click.echo("Error: {}.".format(e), err=True)
    
    63
    +        sys.exit(-1)
    
    66 64
     
    
    67
    -        context.channel = grpc.secure_channel(context.remote, credentials)
    
    68
    -
    
    69
    -    click.echo("Starting for remote=[{}]".format(context.remote))
    
    65
    +    context.instance_name = instance_name
    
    70 66
     
    
    71 67
     
    
    72 68
     def _print_operation_status(operation, print_details=False):
    

  • buildgrid/server/instance.py
    ... ... @@ -29,6 +29,7 @@ import janus
    29 29
     from buildgrid._enums import BotStatus, LogRecordLevel, MetricRecordDomain, MetricRecordType
    
    30 30
     from buildgrid._protos.buildgrid.v2 import monitoring_pb2
    
    31 31
     from buildgrid.server.actioncache.service import ActionCacheService
    
    32
    +from buildgrid.server._authentication import AuthMetadataMethod, AuthMetadataAlgorithm, AuthMetadataServerInterceptor
    
    32 33
     from buildgrid.server.bots.service import BotsService
    
    33 34
     from buildgrid.server.capabilities.instance import CapabilitiesInstance
    
    34 35
     from buildgrid.server.capabilities.service import CapabilitiesService
    
    ... ... @@ -47,11 +48,21 @@ class BuildGridServer:
    47 48
         requisite services.
    
    48 49
         """
    
    49 50
     
    
    50
    -    def __init__(self, max_workers=None, monitor=False):
    
    51
    +    def __init__(self, max_workers=None, monitor=False, auth_method=AuthMetadataMethod.NONE,
    
    52
    +                 auth_secret=None, auth_algorithm=AuthMetadataAlgorithm.NONE):
    
    51 53
             """Initializes a new :class:`BuildGridServer` instance.
    
    52 54
     
    
    53 55
             Args:
    
    54 56
                 max_workers (int, optional): A pool of max worker threads.
    
    57
    +            monitor (bool, optional): Whether or not to globally activate server
    
    58
    +                monitoring. Defaults to ``False``.
    
    59
    +            auth_method (AuthMetadataMethod, optional): Authentication method to
    
    60
    +                be used for request authorization. Defaults to ``NONE``.
    
    61
    +            auth_secret (str, optional): The secret or key to be used for
    
    62
    +                authorizing request using `auth_method`. Defaults to ``None``.
    
    63
    +            auth_algorithm (AuthMetadataAlgorithm, optional): The crytographic
    
    64
    +                algorithm to be uses in combination with `auth_secret` for
    
    65
    +                authorizing request using `auth_method`. Defaults to ``NONE``.
    
    55 66
             """
    
    56 67
             self.__logger = logging.getLogger(__name__)
    
    57 68
     
    
    ... ... @@ -59,8 +70,17 @@ class BuildGridServer:
    59 70
                 # Use max_workers default from Python 3.5+
    
    60 71
                 max_workers = (os.cpu_count() or 1) * 5
    
    61 72
     
    
    73
    +        self.__grpc_auth_interceptor = None
    
    74
    +        if auth_method != AuthMetadataMethod.NONE:
    
    75
    +            self.__grpc_auth_interceptor = AuthMetadataServerInterceptor(
    
    76
    +                method=auth_method, secret=auth_secret, algorithm=auth_algorithm)
    
    62 77
             self.__grpc_executor = futures.ThreadPoolExecutor(max_workers)
    
    63
    -        self.__grpc_server = grpc.server(self.__grpc_executor)
    
    78
    +
    
    79
    +        if self.__grpc_auth_interceptor is not None:
    
    80
    +            self.__grpc_server = grpc.server(
    
    81
    +                self.__grpc_executor, interceptors=(self.__grpc_auth_interceptor,))
    
    82
    +        else:
    
    83
    +            self.__grpc_server = grpc.server(self.__grpc_executor)
    
    64 84
     
    
    65 85
             self.__main_loop = asyncio.get_event_loop()
    
    66 86
     
    

  • tests/auth/__init__.py

  • tests/auth/data/jwt-hs256-conflicting.secret
    1
    +not-your-256-bit-secret
    \ No newline at end of file

  • tests/auth/data/jwt-hs256-expired.token
    1
    +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCdWlsZEdyaWQgVGVzdCIsImlhdCI6MTM1NTMxNDMzMiwiZXhwIjoxMzU1MzE0MzMyfQ.J-YxkvVbeZFwZ2_mK1d91Wb5vm481LkuehfkUoNpLb8
    \ No newline at end of file

  • tests/auth/data/jwt-hs256-matching.secret
    1
    +your-256-bit-secret

  • tests/auth/data/jwt-hs256-unbounded.token
    1
    +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCdWlsZEdyaWQgVGVzdCIsImlhdCI6MTM1NTMxNDMzMn0.tEBYb8GM_4lmJCxrh6iMhdoxupgGTCaJR3atLxodZV8
    \ No newline at end of file

  • tests/auth/data/jwt-hs256-valid.token
    1
    +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCdWlsZEdyaWQgVGVzdCIsImlhdCI6MTM1NTMxNDMzMiwiZXhwIjoyMzAxOTk5MTMyfQ.U__BJgksk65S0YuDZqU85tRWF3F03ITF9HXW1cd_Ci8
    \ No newline at end of file

  • tests/auth/data/jwt-rs256-conflicting.pub.key
    1
    +-----BEGIN PUBLIC KEY-----
    
    2
    +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNGWI+5SgYIiGcS5UNQzZOs5As
    
    3
    +QJRsKR7bBiMjJWnqrNExn0pYrecQsBy6zgwmN6MqEPFV5A1GOEeP3FAqlwD5y+rL
    
    4
    +iO2tqq0naiFiJb27qsHzgjakw7pVqgJuGuLVIWWE1RlDnhfN+auNGWyl2YjF6N2+
    
    5
    +Lsl0bBX8Q8zaOxbU3wIDAQAB
    
    6
    +-----END PUBLIC KEY-----

  • tests/auth/data/jwt-rs256-expired.token
    1
    +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCdWlsZEdyaWQgVGVzdCIsImlhdCI6MTM1NTMxNDMzMiwiZXhwIjoxMzU1MzE0MzMyfQ.L9Wg6YDEV57_a_j_J2s6ug5eEGS80SvlwztdVFzU1ajqlfYmbF5oYq6IEMaiZq95aKkJ71xnwpGfpcuufH-xONiNoZhTg7r-lb99yvPZ8VEHwOIyt1EUEziffim9XRiuwM570fg7_HUC-ZhNJG536k1IM-6rPRnv-1Tu-MxLgvQ
    \ No newline at end of file

  • tests/auth/data/jwt-rs256-matching.priv.key
    1
    +-----BEGIN RSA PRIVATE KEY-----
    
    2
    +MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw
    
    3
    +33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW
    
    4
    ++jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
    
    5
    +AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS
    
    6
    +3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp
    
    7
    +uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE
    
    8
    +2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0
    
    9
    +GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K
    
    10
    +Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY
    
    11
    +6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5
    
    12
    +fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523
    
    13
    +Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP
    
    14
    +FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==
    
    15
    +-----END RSA PRIVATE KEY-----
    \ No newline at end of file

  • tests/auth/data/jwt-rs256-matching.pub.key
    1
    +-----BEGIN PUBLIC KEY-----
    
    2
    +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd
    
    3
    +UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs
    
    4
    +HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D
    
    5
    +o2kQ+X5xK9cipRgEKwIDAQAB
    
    6
    +-----END PUBLIC KEY-----

  • tests/auth/data/jwt-rs256-unbounded.token
    1
    +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCdWlsZEdyaWQgVGVzdCIsImlhdCI6MTM1NTMxNDMzMn0.1_yoF5Vg1fXs2SmWhYm7LbLMGbZVgBjxZkzRlw87blSG6lRgEi_R-WXKcS4n_pGynkcahJ_AqLseyHduXZveI1nVQFATXVNQQPcvmkM6pYSHPm155iqZFdYAWWVKB9ND1F3oDrXLBzqF2a4HtLNXYTu5gDStdUPkrH_FiatX79g
    \ No newline at end of file

  • tests/auth/data/jwt-rs256-valid.token
    1
    +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCdWlsZEdyaWQgVGVzdCIsImlhdCI6MTM1NTMxNDMzMiwiZXhwIjoyMzAxOTk5MTMyfQ.RCbQNqPaF0mfFsUGwdb47Ga3DITAL7OjYlcWkJ2xWL61Fo9zURx_mSIVDYTgEY3nFW1cmf2r6Y2Z0rEUOY-qBl8B9Ww9jijGz7LMR4w_j8f967MjTxkyOCWZSUWazObg5jxNjLtfxPD28UbbrS_2R8BLgMQFf3ymSDXTX5lAdBY
    \ No newline at end of file

  • tests/auth/test_client.py
    1
    +# Copyright (C) 2018 Bloomberg LP
    
    2
    +#
    
    3
    +# Licensed under the Apache License, Version 2.0 (the "License");
    
    4
    +# you may not use this file except in compliance with the License.
    
    5
    +# You may obtain a copy of the License at
    
    6
    +#
    
    7
    +#  <http://www.apache.org/licenses/LICENSE-2.0>
    
    8
    +#
    
    9
    +# Unless required by applicable law or agreed to in writing, software
    
    10
    +# distributed under the License is distributed on an "AS IS" BASIS,
    
    11
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    
    12
    +# See the License for the specific language governing permissions and
    
    13
    +# limitations under the License.
    
    14
    +
    
    15
    +# pylint: disable=redefined-outer-name
    
    16
    +
    
    17
    +
    
    18
    +import os
    
    19
    +
    
    20
    +import grpc
    
    21
    +import pytest
    
    22
    +
    
    23
    +from buildgrid.client.authentication import setup_channel
    
    24
    +from buildgrid._protos.google.bytestream import bytestream_pb2, bytestream_pb2_grpc
    
    25
    +from buildgrid.server._authentication import AuthMetadataMethod, AuthMetadataAlgorithm
    
    26
    +
    
    27
    +from ..utils.dummy import serve_dummy
    
    28
    +from ..utils.utils import run_in_subprocess
    
    29
    +
    
    30
    +
    
    31
    +try:
    
    32
    +    import jwt  # pylint: disable=unused-import
    
    33
    +except ImportError:
    
    34
    +    HAVE_JWT = False
    
    35
    +else:
    
    36
    +    HAVE_JWT = True
    
    37
    +
    
    38
    +
    
    39
    +DATA_DIR = os.path.join(
    
    40
    +    os.path.dirname(os.path.realpath(__file__)), 'data')
    
    41
    +
    
    42
    +METHOD = AuthMetadataMethod.JWT
    
    43
    +TOKEN = os.path.join(DATA_DIR, 'jwt-hs256-valid.token')
    
    44
    +SECRET = 'your-256-bit-secret'
    
    45
    +ALGORITHM = AuthMetadataAlgorithm.JWT_HS256
    
    46
    +
    
    47
    +
    
    48
    +@pytest.mark.skipif(not HAVE_JWT, reason="No pyjwt")
    
    49
    +def test_channel_token_authorization():
    
    50
    +    # Actual test function, to be run in a subprocess:
    
    51
    +    def __test_channel_token_authorization(queue, remote, token):
    
    52
    +        channel, _ = setup_channel(remote, auth_token=token)
    
    53
    +        stub = bytestream_pb2_grpc.ByteStreamStub(channel)
    
    54
    +
    
    55
    +        request = bytestream_pb2.QueryWriteStatusRequest()
    
    56
    +        status_code = grpc.StatusCode.OK
    
    57
    +
    
    58
    +        try:
    
    59
    +            next(stub.Read(request))
    
    60
    +
    
    61
    +        except grpc.RpcError as e:
    
    62
    +            status_code = e.code()
    
    63
    +
    
    64
    +        queue.put(status_code)
    
    65
    +
    
    66
    +    with serve_dummy(auth_method=METHOD, auth_secret=SECRET,
    
    67
    +                     auth_algorithm=ALGORITHM) as server:
    
    68
    +        status = run_in_subprocess(__test_channel_token_authorization,
    
    69
    +                                   server.remote, TOKEN)
    
    70
    +
    
    71
    +        assert status != grpc.StatusCode.UNAUTHENTICATED

  • tests/auth/test_interceptor.py
    1
    +# Copyright (C) 2018 Bloomberg LP
    
    2
    +#
    
    3
    +# Licensed under the Apache License, Version 2.0 (the "License");
    
    4
    +# you may not use this file except in compliance with the License.
    
    5
    +# You may obtain a copy of the License at
    
    6
    +#
    
    7
    +#  <http://www.apache.org/licenses/LICENSE-2.0>
    
    8
    +#
    
    9
    +# Unless required by applicable law or agreed to in writing, software
    
    10
    +# distributed under the License is distributed on an "AS IS" BASIS,
    
    11
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    
    12
    +# See the License for the specific language governing permissions and
    
    13
    +# limitations under the License.
    
    14
    +
    
    15
    +# pylint: disable=redefined-outer-name
    
    16
    +
    
    17
    +
    
    18
    +from collections import namedtuple
    
    19
    +from unittest import mock
    
    20
    +import os
    
    21
    +
    
    22
    +import grpc
    
    23
    +from grpc._server import _Context
    
    24
    +import pytest
    
    25
    +
    
    26
    +from buildgrid.server._authentication import AuthMetadataMethod, AuthMetadataAlgorithm
    
    27
    +from buildgrid.server._authentication import AuthMetadataServerInterceptor
    
    28
    +
    
    29
    +from ..utils.utils import read_file
    
    30
    +
    
    31
    +
    
    32
    +try:
    
    33
    +    import jwt  # pylint: disable=unused-import
    
    34
    +except ImportError:
    
    35
    +    HAVE_JWT = False
    
    36
    +else:
    
    37
    +    HAVE_JWT = True
    
    38
    +
    
    39
    +
    
    40
    +DATA_DIR = os.path.join(
    
    41
    +    os.path.dirname(os.path.realpath(__file__)), 'data')
    
    42
    +
    
    43
    +TOKENS = [None, 'not-a-token']
    
    44
    +SECRETS = [None, None]
    
    45
    +ALGORITHMS = [AuthMetadataAlgorithm.NONE, AuthMetadataAlgorithm.NONE]
    
    46
    +VALIDITIES = [False, False]
    
    47
    +# Generic test data: token, secret, algorithm, validity:
    
    48
    +DATA = zip(TOKENS, SECRETS, ALGORITHMS, VALIDITIES)
    
    49
    +
    
    50
    +JWT_TOKENS = [
    
    51
    +    'jwt-hs256-expired.token',
    
    52
    +    'jwt-hs256-unbounded.token',
    
    53
    +    'jwt-hs256-valid.token',
    
    54
    +    'jwt-hs256-valid.token',
    
    55
    +
    
    56
    +    'jwt-rs256-expired.token',
    
    57
    +    'jwt-rs256-unbounded.token',
    
    58
    +    'jwt-rs256-valid.token',
    
    59
    +    'jwt-rs256-valid.token']
    
    60
    +JWT_SECRETS = [
    
    61
    +    'jwt-hs256-matching.secret',
    
    62
    +    'jwt-hs256-matching.secret',
    
    63
    +    'jwt-hs256-matching.secret',
    
    64
    +    'jwt-hs256-conflicting.secret',
    
    65
    +
    
    66
    +    'jwt-rs256-matching.pub.key',
    
    67
    +    'jwt-rs256-matching.pub.key',
    
    68
    +    'jwt-rs256-matching.pub.key',
    
    69
    +    'jwt-rs256-conflicting.pub.key']
    
    70
    +JWT_ALGORITHMS = [
    
    71
    +    AuthMetadataAlgorithm.JWT_HS256,
    
    72
    +    AuthMetadataAlgorithm.JWT_HS256,
    
    73
    +    AuthMetadataAlgorithm.JWT_HS256,
    
    74
    +    AuthMetadataAlgorithm.JWT_HS256,
    
    75
    +
    
    76
    +    AuthMetadataAlgorithm.JWT_RS256,
    
    77
    +    AuthMetadataAlgorithm.JWT_RS256,
    
    78
    +    AuthMetadataAlgorithm.JWT_RS256,
    
    79
    +    AuthMetadataAlgorithm.JWT_RS256]
    
    80
    +JWT_VALIDITIES = [
    
    81
    +    False, False, True, False,
    
    82
    +    False, False, True, False]
    
    83
    +# JWT test data: token, secret, algorithm, validity:
    
    84
    +JWT_DATA = zip(JWT_TOKENS, JWT_SECRETS, JWT_ALGORITHMS, JWT_VALIDITIES)
    
    85
    +
    
    86
    +
    
    87
    +_MockHandlerCallDetails = namedtuple(
    
    88
    +    '_MockHandlerCallDetails', ('method', 'invocation_metadata',))
    
    89
    +_MockMetadatum = namedtuple(
    
    90
    +    '_MockMetadatum', ('key', 'value',))
    
    91
    +
    
    92
    +
    
    93
    +def _mock_call_details(token, method='TestMethod'):
    
    94
    +    invocation_metadata = [
    
    95
    +        _MockMetadatum(
    
    96
    +            key='user-agent',
    
    97
    +            value='grpc-c/6.0.0 (manylinux; chttp2; gao)')]
    
    98
    +
    
    99
    +    if token and token.count('.') == 2:
    
    100
    +        invocation_metadata.append(_MockMetadatum(
    
    101
    +            key='authorization', value='Bearer {}'.format(token)))
    
    102
    +
    
    103
    +    elif token:
    
    104
    +        invocation_metadata.append(_MockMetadatum(
    
    105
    +            key='authorization', value=token))
    
    106
    +
    
    107
    +    return _MockHandlerCallDetails(
    
    108
    +        method=method, invocation_metadata=invocation_metadata)
    
    109
    +
    
    110
    +
    
    111
    +def _unary_unary_rpc_terminator(details):
    
    112
    +
    
    113
    +    def terminate(ignored_request, context):
    
    114
    +        context.set_code(grpc.StatusCode.OK)
    
    115
    +
    
    116
    +    return grpc.unary_unary_rpc_method_handler(terminate)
    
    117
    +
    
    118
    +
    
    119
    +@pytest.mark.parametrize('token,secret,algorithm,validity', DATA)
    
    120
    +def test_authorization(token, secret, algorithm, validity):
    
    121
    +    interceptor = AuthMetadataServerInterceptor(
    
    122
    +        method=AuthMetadataMethod.NONE, secret=secret, algorithm=algorithm)
    
    123
    +
    
    124
    +    call_details = _mock_call_details(token)
    
    125
    +    context = mock.create_autospec(_Context, spec_set=True)
    
    126
    +
    
    127
    +    try:
    
    128
    +        handler = interceptor.intercept_service(None, call_details)
    
    129
    +
    
    130
    +    except AssertionError:
    
    131
    +        context.set_code(grpc.StatusCode.OK)
    
    132
    +
    
    133
    +    else:
    
    134
    +        handler.unary_unary(None, context)
    
    135
    +
    
    136
    +    if validity:
    
    137
    +        context.set_code.assert_called_once_with(grpc.StatusCode.OK)
    
    138
    +        context.abort.assert_not_called()
    
    139
    +
    
    140
    +    else:
    
    141
    +        context.abort.assert_called_once_with(grpc.StatusCode.UNAUTHENTICATED, mock.ANY)
    
    142
    +        context.set_code.assert_not_called()
    
    143
    +
    
    144
    +
    
    145
    +@pytest.mark.skipif(not HAVE_JWT, reason="No pyjwt")
    
    146
    +@pytest.mark.parametrize('token,secret,algorithm,validity', JWT_DATA)
    
    147
    +def test_jwt_authorization(token, secret, algorithm, validity):
    
    148
    +    token = read_file(os.path.join(DATA_DIR, token), text_mode=True).strip()
    
    149
    +    secret = read_file(os.path.join(DATA_DIR, secret), text_mode=True).strip()
    
    150
    +
    
    151
    +    interceptor = AuthMetadataServerInterceptor(
    
    152
    +        method=AuthMetadataMethod.JWT, secret=secret, algorithm=algorithm)
    
    153
    +
    
    154
    +    continuator = _unary_unary_rpc_terminator
    
    155
    +    call_details = _mock_call_details(token)
    
    156
    +    context = mock.create_autospec(_Context, spec_set=True)
    
    157
    +
    
    158
    +    handler = interceptor.intercept_service(continuator, call_details)
    
    159
    +    handler.unary_unary(None, context)
    
    160
    +
    
    161
    +    if validity:
    
    162
    +        context.set_code.assert_called_once_with(grpc.StatusCode.OK)
    
    163
    +        context.abort.assert_not_called()
    
    164
    +
    
    165
    +    else:
    
    166
    +        context.abort.assert_called_once_with(grpc.StatusCode.UNAUTHENTICATED, mock.ANY)
    
    167
    +        context.set_code.assert_not_called()

  • tests/utils/dummy.py
    1
    +# Copyright (C) 2018 Bloomberg LP
    
    2
    +#
    
    3
    +# Licensed under the Apache License, Version 2.0 (the "License");
    
    4
    +# you may not use this file except in compliance with the License.
    
    5
    +# You may obtain a copy of the License at
    
    6
    +#
    
    7
    +#  <http://www.apache.org/licenses/LICENSE-2.0>
    
    8
    +#
    
    9
    +# Unless required by applicable law or agreed to in writing, software
    
    10
    +# distributed under the License is distributed on an "AS IS" BASIS,
    
    11
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    
    12
    +# See the License for the specific language governing permissions and
    
    13
    +# limitations under the License.
    
    14
    +
    
    15
    +
    
    16
    +from concurrent import futures
    
    17
    +from contextlib import contextmanager
    
    18
    +import multiprocessing
    
    19
    +import os
    
    20
    +import signal
    
    21
    +
    
    22
    +import grpc
    
    23
    +import pytest_cov
    
    24
    +
    
    25
    +from buildgrid.server._authentication import AuthMetadataMethod, AuthMetadataAlgorithm
    
    26
    +from buildgrid.server._authentication import AuthMetadataServerInterceptor
    
    27
    +
    
    28
    +
    
    29
    +@contextmanager
    
    30
    +def serve_dummy(auth_method=AuthMetadataMethod.NONE,
    
    31
    +                auth_secret=None, auth_algorithm=AuthMetadataAlgorithm.NONE):
    
    32
    +    server = Server(
    
    33
    +        auth_method=auth_method, auth_secret=auth_secret, auth_algorithm=auth_algorithm)
    
    34
    +    try:
    
    35
    +        yield server
    
    36
    +    finally:
    
    37
    +        server.quit()
    
    38
    +
    
    39
    +
    
    40
    +class Server:
    
    41
    +
    
    42
    +    def __init__(self, auth_method=AuthMetadataMethod.NONE,
    
    43
    +                 auth_secret=None, auth_algorithm=AuthMetadataAlgorithm.NONE):
    
    44
    +
    
    45
    +        self.__queue = multiprocessing.Queue()
    
    46
    +        self.__auth_interceptor = None
    
    47
    +        if auth_method != AuthMetadataMethod.NONE:
    
    48
    +            self.__auth_interceptor = AuthMetadataServerInterceptor(
    
    49
    +                method=auth_method, secret=auth_secret, algorithm=auth_algorithm)
    
    50
    +        self.__process = multiprocessing.Process(
    
    51
    +            target=Server.serve, args=(self.__queue, self.__auth_interceptor))
    
    52
    +        self.__process.start()
    
    53
    +
    
    54
    +        self.port = self.__queue.get()
    
    55
    +        self.remote = 'http://localhost:{}'.format(self.port)
    
    56
    +
    
    57
    +    @classmethod
    
    58
    +    def serve(cls, queue, auth_interceptor):
    
    59
    +        pytest_cov.embed.cleanup_on_sigterm()
    
    60
    +
    
    61
    +        # Use max_workers default from Python 3.5+
    
    62
    +        max_workers = (os.cpu_count() or 1) * 5
    
    63
    +        executor = futures.ThreadPoolExecutor(max_workers)
    
    64
    +        if auth_interceptor is not None:
    
    65
    +            server = grpc.server(executor, interceptors=(auth_interceptor,))
    
    66
    +        else:
    
    67
    +            server = grpc.server(executor)
    
    68
    +        port = server.add_insecure_port('localhost:0')
    
    69
    +
    
    70
    +        queue.put(port)
    
    71
    +
    
    72
    +        server.start()
    
    73
    +
    
    74
    +        signal.pause()
    
    75
    +
    
    76
    +    def quit(self):
    
    77
    +        if self.__process:
    
    78
    +            self.__process.terminate()
    
    79
    +            self.__process.join()

  • tests/utils/utils.py
    ... ... @@ -34,6 +34,11 @@ def kill_process_tree(pid):
    34 34
         kill_proc(proc)
    
    35 35
     
    
    36 36
     
    
    37
    +def read_file(file_path, text_mode=False):
    
    38
    +    with open(file_path, 'r' if text_mode else 'r') as in_file:
    
    39
    +        return in_file.read()
    
    40
    +
    
    41
    +
    
    37 42
     def run_in_subprocess(function, *arguments, timeout=1):
    
    38 43
         queue = multiprocessing.Queue()
    
    39 44
         # Use subprocess to avoid creation of gRPC threads in main process
    



  • [Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]