diff options
-rw-r--r-- | morphlib/__init__.py | 1 | ||||
-rw-r--r-- | morphlib/artifact.py | 6 | ||||
-rw-r--r-- | morphlib/localartifactcache.py | 3 | ||||
-rw-r--r-- | morphlib/remoteartifactcache.py | 98 | ||||
-rw-r--r-- | morphlib/remoteartifactcache_tests.py | 156 |
5 files changed, 262 insertions, 2 deletions
diff --git a/morphlib/__init__.py b/morphlib/__init__.py index da08ac32..942ef741 100644 --- a/morphlib/__init__.py +++ b/morphlib/__init__.py @@ -40,6 +40,7 @@ import localrepocache import morph2 import morphology import morphologyloader +import remoteartifactcache import remoterepocache import savefile import source diff --git a/morphlib/artifact.py b/morphlib/artifact.py index 3a438747..cea2f2f3 100644 --- a/morphlib/artifact.py +++ b/morphlib/artifact.py @@ -53,5 +53,11 @@ class Artifact(object): self.source.morphology['kind'], self.name) + def metadata_basename(self, metadata_name): # pragma: no cover + return '%s.%s.%s.%s' % (self.cache_key, + self.source.morphology['kind'], + self.name, + metadata_name) + def __str__(self): # pragma: no cover return '%s|%s' % (self.source, self.name) diff --git a/morphlib/localartifactcache.py b/morphlib/localartifactcache.py index cf025210..83a668e4 100644 --- a/morphlib/localartifactcache.py +++ b/morphlib/localartifactcache.py @@ -65,10 +65,9 @@ class LocalArtifactCache(object): return os.path.join(self.cachedir, basename) def _artifact_metadata_filename(self, artifact, name): - basename = '%s.%s' % (artifact.basename(), name) + basename = artifact.metadata_basename(name) return os.path.join(self.cachedir, basename) - def _source_metadata_filename(self, source, cachekey, name): basename = '%s.%s' % (cachekey, name) return os.path.join(self.cachedir, basename) diff --git a/morphlib/remoteartifactcache.py b/morphlib/remoteartifactcache.py new file mode 100644 index 00000000..6df91a4e --- /dev/null +++ b/morphlib/remoteartifactcache.py @@ -0,0 +1,98 @@ +# Copyright (C) 2012 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import cliapp +import httplib2 +import urllib2 +import urlparse + + +class GetError(cliapp.AppException): + + def __init__(self, cache, artifact): + cliapp.AppException.__init__( + self, 'Failed to get the artifact %s from the ' + 'artifact cache %s' % (artifact, cache)) + + +class GetArtifactMetadataError(cliapp.AppException): + + def __init__(self, cache, artifact, name): + cliapp.AppException.__init__( + self, 'Failed to get metadata %s for the artifact %s ' + 'from the artifact cache %s' % (name, artifact, cache)) + + +class GetSourceMetadataError(cliapp.AppException): + + def __init__(self, cache, source, cache_key, name): + cliapp.AppException.__init__( + self, 'Failed to get metadata %s for source %s ' + 'and cache key %s from the artifact cache %s' % + (name, source, cache_key, cache)) + + +class RemoteArtifactCache(object): + + def __init__(self, server_url): + self.server_url = server_url + + def has(self, artifact): + return self._has_file(artifact.basename()) + + def has_artifact_metadata(self, artifact, name): + return self._has_file(artifact.metadata_basename(name)) + + def has_source_metadata(self, source, cachekey, name): + filename = '%s.%s' % (cachekey, name) + return self._has_file(filename) + + def get(self, artifact): + try: + return self._get_file(artifact.basename()) + except: + raise GetError(self, artifact) + + def get_artifact_metadata(self, artifact, name): + try: + return self._get_file(artifact.metadata_basename(name)) + except: + raise GetArtifactMetadataError(self, artifact, name) + + def get_source_metadata(self, source, cachekey, name): + filename = '%s.%s' % (cachekey, name) + try: + return self._get_file(filename) + except: + raise GetSourceMetadataError(self, source, cachekey, name) + + def _has_file(self, filename): # pragma: no cover + url = self._request_url(filename) + http = httplib2.Http() + response = http.request(url, 'HEAD') + status = response[0]['status'] + return status >= 200 and status < 400 + + def _get_file(self, filename): # pragma: no cover + url = self._request_url(filename) + return urllib2.urlopen(url) + + def _request_url(self, filename): # pragma: no cover + server_url = self.server_url + if not server_url.endswith('/'): + server_url += '/' + return urlparse.urljoin( + server_url, '/1.0/artifacts/filename=%s' % filename) diff --git a/morphlib/remoteartifactcache_tests.py b/morphlib/remoteartifactcache_tests.py new file mode 100644 index 00000000..b7f10450 --- /dev/null +++ b/morphlib/remoteartifactcache_tests.py @@ -0,0 +1,156 @@ +# Copyright (C) 2012 Codethink Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import StringIO +import unittest + +import morphlib + + +class RemoteArtifactCacheTests(unittest.TestCase): + + def setUp(self): + morph = morphlib.morph2.Morphology( + ''' + { + "chunk": "chunk", + "kind": "chunk", + "artifacts": { + "chunk-runtime": [ + "usr/bin", + "usr/sbin", + "usr/lib", + "usr/libexec" + ], + "chunk-devel": [ + "usr/include" + ], + "chunk-doc": [ + "usr/share/doc" + ] + } + } + ''') + self.source = morphlib.source.Source( + 'repo', 'ref', 'sha1', morph, 'chunk.morph') + self.runtime_artifact = morphlib.artifact.Artifact( + self.source, 'chunk-runtime') + self.runtime_artifact.cache_key = 'CHUNK-RUNTIME' + self.devel_artifact = morphlib.artifact.Artifact( + self.source, 'chunk-devel') + self.devel_artifact.cache_key = 'CHUNK-DEVEL' + self.doc_artifact = morphlib.artifact.Artifact( + self.source, 'chunk-doc') + self.doc_artifact.cache_key = 'CHUNK-DOC' + + self.existing_files = set([ + self.runtime_artifact.basename(), + self.devel_artifact.basename(), + self.runtime_artifact.metadata_basename('meta'), + '%s.%s' % (self.runtime_artifact.cache_key, 'meta'), + ]) + + self.server_url = 'http://foo.bar:8080' + self.cache = morphlib.remoteartifactcache.RemoteArtifactCache( + self.server_url) + self.cache._has_file = self._has_file + self.cache._get_file = self._get_file + + def _has_file(self, filename): + return filename in self.existing_files + + def _get_file(self, filename): + if filename in self.existing_files: + return StringIO.StringIO('%s' % filename) + else: + raise Exception('foo') + + def test_sets_server_url(self): + self.assertEqual(self.cache.server_url, self.server_url) + + def test_has_existing_artifact(self): + self.assertTrue(self.cache.has(self.runtime_artifact)) + + def test_has_a_different_existing_artifact(self): + self.assertTrue(self.cache.has(self.devel_artifact)) + + def test_does_not_have_a_non_existent_artifact(self): + self.assertFalse(self.cache.has(self.doc_artifact)) + + def test_has_existing_artifact_metadata(self): + self.assertTrue(self.cache.has_artifact_metadata( + self.runtime_artifact, 'meta')) + + def test_does_not_have_non_existent_artifact_metadata(self): + self.assertFalse(self.cache.has_artifact_metadata( + self.runtime_artifact, 'non-existent-meta')) + + def test_has_existing_source_metadata(self): + self.assertTrue(self.cache.has_source_metadata( + self.runtime_artifact.source, + self.runtime_artifact.cache_key, + 'meta')) + + def test_does_not_have_non_existent_source_metadata(self): + self.assertFalse(self.cache.has_source_metadata( + self.runtime_artifact.source, + self.runtime_artifact.cache_key, + 'non-existent-meta')) + + def test_get_existing_artifact(self): + handle = self.cache.get(self.runtime_artifact) + data = handle.read() + self.assertEqual(data, self.runtime_artifact.basename()) + + def test_get_a_different_existing_artifact(self): + handle = self.cache.get(self.devel_artifact) + data = handle.read() + self.assertEqual(data, self.devel_artifact.basename()) + + def test_fails_to_get_a_non_existent_artifact(self): + self.assertRaises(morphlib.remoteartifactcache.GetError, + self.cache.get, self.doc_artifact) + + def test_get_existing_artifact_metadata(self): + handle = self.cache.get_artifact_metadata( + self.runtime_artifact, 'meta') + data = handle.read() + self.assertEqual( + data, '%s.%s' % (self.runtime_artifact.basename(), 'meta')) + + def test_fails_to_get_non_existent_artifact_metadata(self): + self.assertRaises( + morphlib.remoteartifactcache.GetArtifactMetadataError, + self.cache.get_artifact_metadata, + self.runtime_artifact, + 'non-existent-meta') + + def test_get_existing_source_metadata(self): + handle = self.cache.get_source_metadata( + self.runtime_artifact.source, + self.runtime_artifact.cache_key, + 'meta') + data = handle.read() + self.assertEqual( + data, '%s.%s' % (self.runtime_artifact.cache_key, 'meta')) + + def test_fails_to_get_non_existent_source_metadata(self): + self.assertRaises( + morphlib.remoteartifactcache.GetSourceMetadataError, + self.cache.get_source_metadata, + self.runtime_artifact.source, + self.runtime_artifact.cache_key, + 'non-existent-meta') |