richardmaw-codethink pushed to branch richardmaw/builddir-sockets at BuildStream / buildstream
Commits:
- 
47f3064a
by Jürg Billeter at 2018-09-10T15:43:25Z
- 
1a7fb3cb
by Jürg Billeter at 2018-09-10T15:43:25Z
- 
b3ffcdc8
by Jürg Billeter at 2018-09-10T16:07:30Z
- 
6f925bcb
by Javier Jardón at 2018-09-13T07:38:48Z
- 
c6155f8d
by Javier Jardón at 2018-09-13T08:11:54Z
- 
19838a07
by Chandan Singh at 2018-09-13T10:58:38Z
- 
3b81d451
by Chandan Singh at 2018-09-13T12:27:59Z
- 
55956762
by Richard Maw at 2018-09-13T16:50:33Z
- 
fc7f83ac
by richardmaw-codethink at 2018-09-13T17:14:31Z
- 
233a7d83
by Richard Maw at 2018-09-14T08:53:14Z
- 
f06f234a
by Richard Maw at 2018-09-14T08:53:14Z
9 changed files:
- README.rst
- buildstream/_artifactcache/cascache.py
- buildstream/_artifactcache/casserver.py
- buildstream/element.py
- buildstream/utils.py
- doc/source/install_source.rst
- + tests/integration/project/elements/sockets/make-builddir-socket.bst
- + tests/integration/project/elements/sockets/make-install-root-socket.bst
- + tests/integration/sockets.py
Changes:
| ... | ... | @@ -13,6 +13,9 @@ About | 
| 13 | 13 |  .. image:: https://gitlab.com/BuildStream/buildstream/badges/master/coverage.svg?job=coverage
 | 
| 14 | 14 |     :target: https://gitlab.com/BuildStream/buildstream/commits/master
 | 
| 15 | 15 |  | 
| 16 | +.. image:: https://img.shields.io/pypi/v/BuildStream.svg
 | |
| 17 | +   :target: https://pypi.org/project/BuildStream
 | |
| 18 | + | |
| 16 | 19 |  | 
| 17 | 20 |  What is BuildStream?
 | 
| 18 | 21 |  ====================
 | 
| ... | ... | @@ -684,6 +684,9 @@ class CASCache(ArtifactCache): | 
| 684 | 684 |                  symlinknode = directory.symlinks.add()
 | 
| 685 | 685 |                  symlinknode.name = name
 | 
| 686 | 686 |                  symlinknode.target = os.readlink(full_path)
 | 
| 687 | +            elif stat.S_ISSOCK(mode):
 | |
| 688 | +                # The process serving the socket can't be cached anyway
 | |
| 689 | +                pass
 | |
| 687 | 690 |              else:
 | 
| 688 | 691 |                  raise ArtifactError("Unsupported file type for {}".format(full_path))
 | 
| 689 | 692 |  | 
| ... | ... | @@ -38,6 +38,10 @@ from .._context import Context | 
| 38 | 38 |  from .cascache import CASCache
 | 
| 39 | 39 |  | 
| 40 | 40 |  | 
| 41 | +# The default limit for gRPC messages is 4 MiB
 | |
| 42 | +_MAX_BATCH_TOTAL_SIZE_BYTES = 4 * 1024 * 1024
 | |
| 43 | + | |
| 44 | + | |
| 41 | 45 |  # Trying to push an artifact that is too large
 | 
| 42 | 46 |  class ArtifactTooLargeException(Exception):
 | 
| 43 | 47 |      pass
 | 
| ... | ... | @@ -67,6 +71,9 @@ def create_server(repo, *, enable_push): | 
| 67 | 71 |      remote_execution_pb2_grpc.add_ContentAddressableStorageServicer_to_server(
 | 
| 68 | 72 |          _ContentAddressableStorageServicer(artifactcache), server)
 | 
| 69 | 73 |  | 
| 74 | +    remote_execution_pb2_grpc.add_CapabilitiesServicer_to_server(
 | |
| 75 | +        _CapabilitiesServicer(), server)
 | |
| 76 | + | |
| 70 | 77 |      buildstream_pb2_grpc.add_ReferenceStorageServicer_to_server(
 | 
| 71 | 78 |          _ReferenceStorageServicer(artifactcache, enable_push=enable_push), server)
 | 
| 72 | 79 |  | 
| ... | ... | @@ -229,6 +236,48 @@ class _ContentAddressableStorageServicer(remote_execution_pb2_grpc.ContentAddres | 
| 229 | 236 |                  d.size_bytes = digest.size_bytes
 | 
| 230 | 237 |          return response
 | 
| 231 | 238 |  | 
| 239 | +    def BatchReadBlobs(self, request, context):
 | |
| 240 | +        response = remote_execution_pb2.BatchReadBlobsResponse()
 | |
| 241 | +        batch_size = 0
 | |
| 242 | + | |
| 243 | +        for digest in request.digests:
 | |
| 244 | +            batch_size += digest.size_bytes
 | |
| 245 | +            if batch_size > _MAX_BATCH_TOTAL_SIZE_BYTES:
 | |
| 246 | +                context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
 | |
| 247 | +                return response
 | |
| 248 | + | |
| 249 | +            blob_response = response.responses.add()
 | |
| 250 | +            blob_response.digest.hash = digest.hash
 | |
| 251 | +            blob_response.digest.size_bytes = digest.size_bytes
 | |
| 252 | +            try:
 | |
| 253 | +                with open(self.cas.objpath(digest), 'rb') as f:
 | |
| 254 | +                    if os.fstat(f.fileno()).st_size != digest.size_bytes:
 | |
| 255 | +                        blob_response.status.code = grpc.StatusCode.NOT_FOUND
 | |
| 256 | +                        continue
 | |
| 257 | + | |
| 258 | +                    blob_response.data = f.read(digest.size_bytes)
 | |
| 259 | +            except FileNotFoundError:
 | |
| 260 | +                blob_response.status.code = grpc.StatusCode.NOT_FOUND
 | |
| 261 | + | |
| 262 | +        return response
 | |
| 263 | + | |
| 264 | + | |
| 265 | +class _CapabilitiesServicer(remote_execution_pb2_grpc.CapabilitiesServicer):
 | |
| 266 | +    def GetCapabilities(self, request, context):
 | |
| 267 | +        response = remote_execution_pb2.ServerCapabilities()
 | |
| 268 | + | |
| 269 | +        cache_capabilities = response.cache_capabilities
 | |
| 270 | +        cache_capabilities.digest_function.append(remote_execution_pb2.SHA256)
 | |
| 271 | +        cache_capabilities.action_cache_update_capabilities.update_enabled = False
 | |
| 272 | +        cache_capabilities.max_batch_total_size_bytes = _MAX_BATCH_TOTAL_SIZE_BYTES
 | |
| 273 | +        cache_capabilities.symlink_absolute_path_strategy = remote_execution_pb2.CacheCapabilities.ALLOWED
 | |
| 274 | + | |
| 275 | +        response.deprecated_api_version.major = 2
 | |
| 276 | +        response.low_api_version.major = 2
 | |
| 277 | +        response.high_api_version.major = 2
 | |
| 278 | + | |
| 279 | +        return response
 | |
| 280 | + | |
| 232 | 281 |  | 
| 233 | 282 |  class _ReferenceStorageServicer(buildstream_pb2_grpc.ReferenceStorageServicer):
 | 
| 234 | 283 |      def __init__(self, cas, *, enable_push):
 | 
| ... | ... | @@ -200,7 +200,6 @@ class Element(Plugin): | 
| 200 | 200 |          self.__strict_cache_key = None          # Our cached cache key for strict builds
 | 
| 201 | 201 |          self.__artifacts = artifacts            # Artifact cache
 | 
| 202 | 202 |          self.__consistency = Consistency.INCONSISTENT  # Cached overall consistency state
 | 
| 203 | -        self.__cached = None                    # Whether we have a cached artifact
 | |
| 204 | 203 |          self.__strong_cached = None             # Whether we have a cached artifact
 | 
| 205 | 204 |          self.__weak_cached = None               # Whether we have a cached artifact
 | 
| 206 | 205 |          self.__assemble_scheduled = False       # Element is scheduled to be assembled
 | 
| ... | ... | @@ -1126,8 +1125,6 @@ class Element(Plugin): | 
| 1126 | 1125 |  | 
| 1127 | 1126 |          # Query caches now that the weak and strict cache keys are available
 | 
| 1128 | 1127 |          key_for_cache_lookup = self.__strict_cache_key if context.get_strict() else self.__weak_cache_key
 | 
| 1129 | -        if not self.__cached:
 | |
| 1130 | -            self.__cached = self.__artifacts.contains(self, key_for_cache_lookup)
 | |
| 1131 | 1128 |          if not self.__strong_cached:
 | 
| 1132 | 1129 |              self.__strong_cached = self.__artifacts.contains(self, self.__strict_cache_key)
 | 
| 1133 | 1130 |          if key_for_cache_lookup == self.__weak_cache_key:
 | 
| ... | ... | @@ -2079,7 +2076,7 @@ class Element(Plugin): | 
| 2079 | 2076 |  | 
| 2080 | 2077 |      def __is_cached(self, keystrength):
 | 
| 2081 | 2078 |          if keystrength is None:
 | 
| 2082 | -            return self.__cached
 | |
| 2079 | +            keystrength = _KeyStrength.STRONG if self._get_context().get_strict() else _KeyStrength.WEAK
 | |
| 2083 | 2080 |  | 
| 2084 | 2081 |          return self.__strong_cached if keystrength == _KeyStrength.STRONG else self.__weak_cached
 | 
| 2085 | 2082 |  | 
| ... | ... | @@ -372,6 +372,8 @@ def copy_files(src, dest, *, files=None, ignore_missing=False, report_written=Fa | 
| 372 | 372 |         Directories in `dest` are replaced with files from `src`,
 | 
| 373 | 373 |         unless the existing directory in `dest` is not empty in which
 | 
| 374 | 374 |         case the path will be reported in the return value.
 | 
| 375 | + | |
| 376 | +       UNIX domain socket files from `src` are ignored.
 | |
| 375 | 377 |      """
 | 
| 376 | 378 |      presorted = False
 | 
| 377 | 379 |      if files is None:
 | 
| ... | ... | @@ -414,6 +416,8 @@ def link_files(src, dest, *, files=None, ignore_missing=False, report_written=Fa | 
| 414 | 416 |  | 
| 415 | 417 |         If a hardlink cannot be created due to crossing filesystems,
 | 
| 416 | 418 |         then the file will be copied instead.
 | 
| 419 | + | |
| 420 | +       UNIX domain socket files from `src` are ignored.
 | |
| 417 | 421 |      """
 | 
| 418 | 422 |      presorted = False
 | 
| 419 | 423 |      if files is None:
 | 
| ... | ... | @@ -841,6 +845,13 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result, | 
| 841 | 845 |              os.mknod(destpath, file_stat.st_mode, file_stat.st_rdev)
 | 
| 842 | 846 |              os.chmod(destpath, file_stat.st_mode)
 | 
| 843 | 847 |  | 
| 848 | +        elif stat.S_ISFIFO(mode):
 | |
| 849 | +            os.mkfifo(destpath, mode)
 | |
| 850 | + | |
| 851 | +        elif stat.S_ISSOCK(mode):
 | |
| 852 | +            # We can't duplicate the process serving the socket anyway
 | |
| 853 | +            pass
 | |
| 854 | + | |
| 844 | 855 |          else:
 | 
| 845 | 856 |              # Unsupported type.
 | 
| 846 | 857 |              raise UtilError('Cannot extract {} into staging-area. Unsupported type.'.format(srcpath))
 | 
| ... | ... | @@ -29,6 +29,7 @@ The default plugins with extra host dependencies are: | 
| 29 | 29 |  * git
 | 
| 30 | 30 |  * ostree
 | 
| 31 | 31 |  * patch
 | 
| 32 | +* pip
 | |
| 32 | 33 |  * tar
 | 
| 33 | 34 |  | 
| 34 | 35 |  If you intend to push built artifacts to a remote artifact server,
 | 
| 1 | +kind: manual
 | |
| 2 | + | |
| 3 | +depends:
 | |
| 4 | +- filename: base.bst
 | |
| 5 | +  type: build
 | |
| 6 | + | |
| 7 | +config:
 | |
| 8 | +  build-commands:
 | |
| 9 | +    - |
 | |
| 10 | +      python3 -c '
 | |
| 11 | +      from socket import socket, AF_UNIX, SOCK_STREAM
 | |
| 12 | +      s = socket(AF_UNIX, SOCK_STREAM)
 | |
| 13 | +      s.bind("testsocket")
 | |
| 14 | +      ' | 
| 1 | +kind: manual
 | |
| 2 | + | |
| 3 | +depends:
 | |
| 4 | +- filename: base.bst
 | |
| 5 | +  type: build
 | |
| 6 | + | |
| 7 | +config:
 | |
| 8 | +  install-commands:
 | |
| 9 | +    - |
 | |
| 10 | +      python3 -c '
 | |
| 11 | +      from os.path import join
 | |
| 12 | +      from sys import argv
 | |
| 13 | +      from socket import socket, AF_UNIX, SOCK_STREAM
 | |
| 14 | +      s = socket(AF_UNIX, SOCK_STREAM)
 | |
| 15 | +      s.bind(join(argv[1], "testsocket"))
 | |
| 16 | +      ' %{install-root} | 
| 1 | +import os
 | |
| 2 | +import pytest
 | |
| 3 | + | |
| 4 | +from buildstream import _yaml
 | |
| 5 | + | |
| 6 | +from tests.testutils import cli_integration as cli
 | |
| 7 | +from tests.testutils.integration import assert_contains
 | |
| 8 | + | |
| 9 | + | |
| 10 | +pytestmark = pytest.mark.integration
 | |
| 11 | + | |
| 12 | +DATA_DIR = os.path.join(
 | |
| 13 | +    os.path.dirname(os.path.realpath(__file__)),
 | |
| 14 | +    "project"
 | |
| 15 | +)
 | |
| 16 | + | |
| 17 | + | |
| 18 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 19 | +def test_builddir_socket_ignored(cli, tmpdir, datafiles):
 | |
| 20 | +    project = os.path.join(datafiles.dirname, datafiles.basename)
 | |
| 21 | +    element_name = 'sockets/make-builddir-socket.bst'
 | |
| 22 | + | |
| 23 | +    result = cli.run(project=project, args=['build', element_name])
 | |
| 24 | +    assert result.exit_code == 0
 | |
| 25 | + | |
| 26 | + | |
| 27 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 28 | +def test_install_root_socket_ignored(cli, tmpdir, datafiles):
 | |
| 29 | +    project = os.path.join(datafiles.dirname, datafiles.basename)
 | |
| 30 | +    element_name = 'sockets/make-install-root-socket.bst'
 | |
| 31 | + | |
| 32 | +    result = cli.run(project=project, args=['build', element_name])
 | |
| 33 | +    assert result.exit_code == 0 | 
