Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cbbbaf0
Add package and repository
colin-msphere Mar 28, 2018
61cc847
Change request header version and content for package and repository
colin-msphere Mar 28, 2018
c723819
Use correct "post" method and correct wait for deployment method
colin-msphere Mar 28, 2018
b97f3aa
Add tests for Repository
colin-msphere Mar 28, 2018
015da59
Fix the repository path
colin-msphere Mar 28, 2018
8c961ff
Improve integration tests for packages
colin-msphere Mar 28, 2018
873492f
Package list operation only accepts v1
colin-msphere Mar 28, 2018
173a7fa
Improvements to package and tests
colin-msphere Mar 28, 2018
ca4c8aa
alignment
colin-msphere Mar 28, 2018
01dd786
Do not wait for package to uninstall
colin-msphere Mar 28, 2018
f146e2b
Update response version for list and describe
colin-msphere Mar 28, 2018
0f4e3ff
Walk back version changes
colin-msphere Mar 29, 2018
d86341f
Raise a TypeError when inputs are not supported types
colin-msphere Mar 30, 2018
7601663
Add more tests to increase coverage
colin-msphere Apr 2, 2018
e1e3dfb
Add more package calls to integration test
colin-msphere Apr 3, 2018
4eac218
Add type hint to package property
colin-msphere Apr 3, 2018
e11a0ee
Fix typo
colin-msphere Apr 3, 2018
9b5a5ee
Use v2 for package describe response
colin-msphere Apr 3, 2018
bbeb2b6
Use Jenkins for package tests
colin-msphere Apr 3, 2018
016771b
Add to repo integration test
colin-msphere Apr 4, 2018
9e57929
Repo integration test now uses existing repos
colin-msphere Apr 5, 2018
374892d
name is required for repo delete
colin-msphere Apr 5, 2018
c29cf76
Check repo listings after each command
colin-msphere Apr 10, 2018
2868438
Wrap debug output in try/catch so it always gets printed
colin-msphere Apr 16, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions dcos_test_utils/dcos_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,12 @@ def cosmos(self):
default_url=self.default_url.copy(path="package"),
session=self.copy().session)

@property
def package(self) -> package.Package:
return package.Package(
default_url=self.default_url.copy(path="package"),
session=self.copy().session)

@property
def health(self):
""" Property which returns a :class:`dcos_test_utils.diagnostics.Diagnostics`
Expand Down
219 changes: 219 additions & 0 deletions dcos_test_utils/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,222 @@ def list_packages(self):
"""
self._update_headers('list')
return self._post('/list', {})


class Repository(Cosmos):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You've went and gone full-python now 👍

def __init__(self, default_url, session=None):
super().__init__(default_url, session=session)

def add(self, name: str, uri: str, index: int = None) -> dict:
"""Add a package repository. If `index` is 0 it will be added to
the top of the repository list.

Args:
name: Repository name
uri: Repository URI
index: Position in repository list, starting at 0

Returns:
dict: JSON response
"""
params = {
'uri': uri,
'name': name,
}

if index is not None:
idx_type = type(index)
if idx_type != int:
raise TypeError('index of type {} is not supported '
'- must be int'.format(idx_type))
params['index'] = index

self._update_headers('repository.add')
r = self._post('/add', params)
return r.json()

def delete(self, name: str, uri: str = None) -> dict:
"""Delete the package repository with given name.

Args:
name: Repository name
uri: Repository URI

Returns:
dict: JSON response
"""
params = {'name': name, }
if uri:
params['uri'] = uri

self._update_headers('repository.delete')
r = self._post('/delete', params)
return r.json()

def list(self) -> dict:
"""Get list of package repositories.

Returns:
dict: JSON response
"""
self._update_headers('repository.list')
r = self._post('/list', {})
return r.json()


class Package(Cosmos):
def __init__(self, default_url, session=None):
super().__init__(default_url, session=session)
self._versions = {
'request_version': '1',
'response_version': '1',
}

def _make_request(self, resource: str, params: dict,
versions: dict = None) -> dict:
"""Every request to /packaging has the same couple of steps.
This puts them in one place instead of duplicating efforts.

Args:
resource: Request and Response versions
params: Package resources (list, install, etc)
versions: POST parameters for the resource

Returns:
dict: JSON response
"""
kw = versions if versions else self._versions
self._update_headers(resource, **kw)
r = self._post("/{}".format(resource), params)
return r.json()

def list(self, name: str = None, app_id: str = None) -> dict:
"""List installed packages.

Args:
name: Package name
app_id: App ID (optional)

Returns:
dict: Installed packages
"""
params = {}
if app_id:
params['appId'] = app_id
if name:
params['packageName'] = name
return self._make_request('list', params)

def install(self, name: str, version: str = None,
options: dict = None, app_id: str = None) -> dict:
"""Install a Universe package.

Args:
name: Package name
version: Package version (optional)
options: Installation options (optional)
app_id: App ID (optional)

Returns:
dict: JSON response
"""
params = {
'packageName': name,
}
if version is not None:
params['packageVersion'] = version

if options is not None:
opt_type = type(options)
if opt_type != dict:
raise TypeError('options of type {} is not supported '
'- must be dict'.format(opt_type))
params['options'] = options

if app_id:
params['appId'] = app_id
return self._make_request('install', params,
{'response_version': '2'})

def uninstall(self, name: str, app_id: str = None) -> dict:
"""Uninstall a Universe package

Args:
name: Package name
app_id: App ID (optional)

Returns:
dict: JSON response
"""
params = {
'packageName': name,
}
if app_id:
params['appId'] = app_id
return self._make_request('uninstall', params)

def describe(self, name: str, version: str = None) -> dict:
"""Show information about a package.

Args:
name: Package name
version: Package version

Returns:
dict: JSON response
"""
params = {
'packageName': name,
}
if version:
params['packageVersion'] = version
return self._make_request('describe', params,
{'response_version': '2'})

def search(self, query: str = None) -> dict:
"""List all packages with a given partial (query).

Args:
query: Partial package query

Returns:
dict: JSON response
"""
params = {}
if query:
params['query'] = query
return self._make_request('search', params)

def list_versions(self, name: str,
include_versions: bool = False) -> dict:
"""List all available versions for a given package.

Args:
name: Package name
include_versions: Include version details

Returns:
JSON response

Raises:
HTTPError

"""
params = {
'packageName': name,
'includePackageVersions': include_versions,
}
return self._make_request('list-versions', params)

@property
def repository(self) -> Repository:
"""Cosmos Repository

Returns:
Repository: Cosmos Repository

"""
repo_path = '{}/repository'.format(self.default_url.path)
return Repository(
default_url=self.default_url.copy(path=repo_path),
session=self.session)
33 changes: 33 additions & 0 deletions integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,36 @@ def test_jobs(dcos_api_session):
assert False
except HTTPError as http_e:
assert http_e.response.status_code == 404


def test_packages(dcos_api_session):
pack_api = dcos_api_session.package
install_resp = pack_api.install('hello-world',
version='2.1.0-0.31.2')
installed_id = install_resp['appId']
dcos_api_session.marathon.wait_for_app_deployment(
installed_id, 1, True, False, 300)
packs = pack_api.list()
found = [p for p in packs['packages']
if p['appId'] == installed_id]
assert found
pack_api.uninstall('hello-world', app_id=installed_id)

pack_api.describe('jenkins')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is there no assert because these methods raise exceptions on failure? Looking through the implementation above it wasn't 100% obvious that _make_request couldn't just return empty when you expected output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These methods will raise an HTTPError when a response is not "OK"-like. Because this is talking to an actual cosmos instance, I did not want to assert on content, as that may change or the server under test may have a custom universe installed (package repo).

A lack of raise is a decent check to ensure the request is valid.

pack_api.search('jenk*')
pack_api.list_versions('jenkins')


def test_repository(dcos_api_session):
repo = dcos_api_session.package.repository

listings = repo.list()['repositories']
assert len(listings) > 0
old_name, old_uri = listings[0]['name'], listings[0]['uri']

repo.delete(old_name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verify the delete? Is this a possibility:

  • repo.delete(old_name) does nothing because of some error
  • repo.add(old_name, old_uri, 0) adds the repo a second time (or it silently skips, IDK the behavior)
  • assert says everything went fine because old_name is still there

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will add this.

listings = repo.list()['repositories']
assert [x for x in listings if x['name'] == old_name] == []
repo.add(old_name, old_uri, 0)
listings = repo.list()['repositories']
assert [x for x in listings if x['name'] == old_name]
69 changes: 69 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from requests import HTTPError

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

+1 abstracting these utilities out



class MockResponse:
def __init__(self, json: dict, status_code: int):
self._json = json
self._status_code = status_code

def json(self):
return self._json

def raise_for_status(self):
"""Throw an HTTPErrer based on status code."""
if self._status_code >= 400:
raise HTTPError('Throwing test error', response=self)

@property
def status_code(self):
return self._status_code

@property
def text(self):
return str(self._json)


class MockEmitter:
"""Emulates a Session and responds with a queued response for each
request made. If no responses are queued, the request will raise
an error.

A history of requests are held in a request cache and can be
reviewed.
"""

def __init__(self, mock_responses: list):
self._mock_responses = mock_responses
self._request_cache = list()

def request(self, *args, **kwargs):
self._request_cache.append((args, kwargs))
return self._mock_responses.pop(0)

def queue(self, response: list):
"""Add responses to the response queue.

:param response: A list of responses
:type response: list
"""
self._mock_responses.extend(response)

@property
def headers(self):
return dict()

@property
def cookies(self):
return dict()

@property
def debug_cache(self):
"""Return the list of requests made during this session.

Items in the cache are formatted:
((Method, URL), params)

:return: a list of requests made to this session
:rtype: list
"""
return self._request_cache
Loading