Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple first steps to create a framework on which to hang real functionality #1

Merged
merged 9 commits into from Jan 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
@@ -0,0 +1,3 @@
[run]
branch = True
include = */txkube/*
32 changes: 32 additions & 0 deletions .travis.yml
@@ -0,0 +1,32 @@
#
# This is the Travis-CI configuration.
#

language: "python"

# This is how you get container-based environments on Travis-CI. And
# container-based environments are how you get fast test runs.
sudo: false

# Only build master; for "push" builds, this is when the branch pushed
# to is master, for "pr" builds, this is when the merge base of the PR
# is master.
branches:
only:
- "master"

cache:
directories:
# Cache the pip download cache across runs to avoid having to
# repeatedly download packages over the network.
- "$HOME/.cache/pip"

install:
- "pip install --upgrade pip setuptools wheel coverage codecov"
- "pip install .[dev]"

script:
- "coverage run --rcfile=${PWD}/.coveragerc $(type -p trial) txkube"

after_success:
- "codecov"
19 changes: 19 additions & 0 deletions README.rst
Expand Up @@ -35,6 +35,25 @@ Usage Sample
d.addCallback(print)
return d

Installing
----------

To install the latest version of txkube using pip::

$ pip install txkube

For additional development dependencies, install the ``dev`` extra::

$ pip install txkube[dev]

Testing
-------

txkube uses pyunit-style tests.
After installing the development dependencies, you can run the test suite with trial::

$ pip install txkube[dev]
$ trial txkube

License
-------
Expand Down
29 changes: 29 additions & 0 deletions setup.py
@@ -0,0 +1,29 @@
#!/usr/bin/env python

# Copyright Least Authority Enterprises.
# See LICENSE for details.

import setuptools

_metadata = {}
with open("src/txkube/_metadata.py") as f:
exec(f.read(), _metadata)

setuptools.setup(
name="txkube",
version=_metadata["version_string"],
description="A Twisted-based Kubernetes client.",
author="txkube Developers",
url="https://github.com/leastauthority.com/txkube",
license="MIT",
package_dir={"": "src"},
packages=setuptools.find_packages(where="src"),
install_requires=[
"incremental", "twisted[tls]",
],
extras_require={
"dev": [
"treq",
Copy link

Choose a reason for hiding this comment

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

I didn't read this carefully.
Add a note to README about how to run the tests.

pip install .
pip install txkube[dev]
trial txkube

],
},
)
21 changes: 21 additions & 0 deletions src/txkube/__init__.py
@@ -0,0 +1,21 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
A Kubernetes client.
"""

__all__ = [
"version",
"IKubernetesClient",
"network_client", "memory_client",
]

from incremental import Version
Copy link

Choose a reason for hiding this comment

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

incremental should probably be listed as a direct dependency in setup.py then.


from ._metadata import version_tuple as _version_tuple
version = Version("txkube", *_version_tuple)

from ._interface import IKubernetes, IKubernetesClient
from ._network import network_kubernetes
from ._memory import memory_kubernetes
36 changes: 36 additions & 0 deletions src/txkube/_interface.py
@@ -0,0 +1,36 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
Explicit interface definitions for txkube.
"""

from zope.interface import Attribute, Interface

class IKubernetes(Interface):
"""
An ``IKubernetes`` provider represents a particular Kubernetes deployment.
"""
base_url = Attribute(
"The root of the Kubernetes HTTP API for this deployment "
"(``twisted.python.url.URL``)."
)
credentials = Attribute(
"The credentials which will grant access to use the "
"deployment's API."
)

def client():
Copy link

Choose a reason for hiding this comment

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

❓ Missing docstring.

"""
Create a client which will interact with the Kubernetes deployment
represented by this object.

:return IKubernetesClient: The client.
"""


class IKubernetesClient(Interface):
Copy link

Choose a reason for hiding this comment

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

❓ Missing docstring.

"""
An ``IKubernetesClient`` provider allows access to the API of a particular
Kubernetes deployment.
"""
57 changes: 57 additions & 0 deletions src/txkube/_memory.py
@@ -0,0 +1,57 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
An in-memory implementation of the Kubernetes client interface.
"""

from zope.interface import implementer

from twisted.python.url import URL

from twisted.web.resource import Resource

from treq.testing import RequestTraversalAgent
Copy link

Choose a reason for hiding this comment

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

When I attempt to run the tests I get this error:

richard@drax:~/.../LeastAuthority/txkube (basics)$ trial txkube
txkube ...                                                              [ERROR]

===============================================================================
[ERROR]
Traceback (most recent call last):
  File "/home/richard/.virtualenvs/txkube/lib/python2.7/site-packages/Twisted-16.7.0rc2-py2.7-linux-x86_64.egg/twisted/trial/runner.py", line 602, in loadByNames
    things.append(self.findByName(name))
  File "/home/richard/.virtualenvs/txkube/lib/python2.7/site-packages/Twisted-16.7.0rc2-py2.7-linux-x86_64.egg/twisted/trial/runner.py", line 406, in findByName
    return reflect.namedAny(name)
  File "/home/richard/.virtualenvs/txkube/lib/python2.7/site-packages/Twisted-16.7.0rc2-py2.7-linux-x86_64.egg/twisted/python/reflect.py", line 301, in namedAny
    topLevelPackage = _importAndCheckStack(trialname)
  File "/home/richard/.virtualenvs/txkube/lib/python2.7/site-packages/Twisted-16.7.0rc2-py2.7-linux-x86_64.egg/twisted/python/reflect.py", line 248, in _importAndCheckStack
    reraise(excValue, excTraceback)
  File "/home/richard/projects/LeastAuthority/txkube/src/txkube/__init__.py", line 21, in <module>
    from ._memory import memory_kubernetes
  File "/home/richard/projects/LeastAuthority/txkube/src/txkube/_memory.py", line 14, in <module>
    from treq.testing import RequestTraversalAgent
exceptions.ImportError: No module named treq.testing

txkube
-------------------------------------------------------------------------------
Ran 1 tests in 0.001s


from . import IKubernetes, network_kubernetes


def memory_kubernetes():
"""
Create an in-memory Kubernetes-alike service.

Copy link

Choose a reason for hiding this comment

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

Missing docstring.

This serves as a places to hold state for stateful Kubernetes interactions
allowed by ``IKubernetesClient``. Only clients created against the same
instance will all share state.

:return IKubernetes: The new Kubernetes-alike service.
"""
return _MemoryKubernetes()


@implementer(IKubernetes)
class _MemoryKubernetes(object):
"""
``_MemoryKubernetes`` maintains state in-memory which approximates
the state of a real Kubernetes deployment sufficiently to expose a
subset of the external Kubernetes API.
"""
def __init__(self):
base_url = URL.fromText(u"https://kubernetes.example.invalid./")
self._resource = _kubernetes_resource()
self._kubernetes = network_kubernetes(
base_url=base_url,
credentials=None,
agent=RequestTraversalAgent(self._resource),
)

def client(self, *args, **kwargs):
"""
:return IKubernetesClient: A new client which interacts with this
object rather than a real Kubernetes deployment.
"""
return self._kubernetes.client(*args, **kwargs)


def _kubernetes_resource():
return Resource()
9 changes: 9 additions & 0 deletions src/txkube/_metadata.py
@@ -0,0 +1,9 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
txkube package metadata definitions.
"""

version_tuple = (17, 1, 0)
version_string = ".".join(map(str, version_tuple))
48 changes: 48 additions & 0 deletions src/txkube/_network.py
@@ -0,0 +1,48 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
A Kubernetes client which uses Twisted to interact with Kubernetes
via HTTP.
"""

from zope.interface import implementer

import attr
from attr import validators

from twisted.python.reflect import namedAny
from twisted.python.url import URL

from twisted.web.iweb import IAgent
from twisted.web.client import Agent

from . import IKubernetes, IKubernetesClient

def network_kubernetes(**kw):
return _NetworkKubernetes(**kw)


@implementer(IKubernetesClient)
@attr.s(frozen=True)
class _NetworkClient(object):
kubernetes = attr.ib(validator=validators.provides(IKubernetes))
agent = attr.ib(validator=validators.provides(IAgent))


@implementer(IKubernetes)
@attr.s(frozen=True)
class _NetworkKubernetes(object):
"""
``_NetworkKubernetes`` knows the location of a particular
Kubernetes deployment and gives out clients which speak to that
deployment.
"""
base_url = attr.ib(validator=validators.instance_of(URL))
credentials = attr.ib()
_agent = attr.ib(
default=attr.Factory(lambda: Agent(namedAny("twisted.internet.reactor"))),
)

def client(self):
return _NetworkClient(self, self._agent)
2 changes: 2 additions & 0 deletions src/txkube/test/__init__.py
@@ -0,0 +1,2 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.
23 changes: 23 additions & 0 deletions src/txkube/test/test_memory.py
@@ -0,0 +1,23 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
Tests for ``txkube.memory_kubernetes``.
"""

from ..testing.integration import kubernetes_client_tests

from .. import memory_kubernetes

def get_kubernetes(case):
"""
Create an in-memory test double provider of ``IKubernetes``.
"""
return memory_kubernetes()


class KubernetesClientIntegrationTests(kubernetes_client_tests(get_kubernetes)):
"""
Integration tests which interact with an in-memory-only Kubernetes
deployment via ``txkube.memory_kubernetes``.
"""
42 changes: 42 additions & 0 deletions src/txkube/test/test_network.py
@@ -0,0 +1,42 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
Tests for ``txkube.network_kubernetes``.

See ``get_kubernetes`` for pre-requisites.
"""

from os import environ

from twisted.python.url import URL

from ..testing.integration import kubernetes_client_tests

from .. import network_kubernetes

def get_kubernetes(case):
"""
Create a real ``IKubernetes`` provider, taking necessary
configuration details from the environment.

To use this set:

- TXKUBE_INTEGRATION_KUBERNETES_BASE_URL
"""
try:
base_url = environ["TXKUBE_INTEGRATION_KUBERNETES_BASE_URL"]
except KeyError:
case.skipTest("Cannot find TXKUBE_INTEGRATION_KUBERNETES_BASE_URL in environment.")
else:
return network_kubernetes(
base_url=URL.fromText(base_url.decode("ascii")),
credentials=None,
)


class KubernetesClientIntegrationTests(kubernetes_client_tests(get_kubernetes)):
"""
Integration tests which interact with a network-accessible
Kubernetes deployment via ``txkube.network_kubernetes``.
"""
2 changes: 2 additions & 0 deletions src/txkube/testing/__init__.py
@@ -0,0 +1,2 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.
25 changes: 25 additions & 0 deletions src/txkube/testing/integration.py
@@ -0,0 +1,25 @@
# Copyright Least Authority Enterprises.
# See LICENSE for details.

"""
Integration test generator for ``txkube.IKubernetesClient``.
"""

from zope.interface.verify import verifyObject

from twisted.trial.unittest import TestCase

from .. import IKubernetesClient


def kubernetes_client_tests(get_kubernetes):
class KubernetesClientIntegrationTests(TestCase):
def test_interfaces(self):
"""
The client provides ``txkube.IKubernetesClient``.
"""
kubernetes = get_kubernetes(self)
client = kubernetes.client()
verifyObject(IKubernetesClient, client)

return KubernetesClientIntegrationTests
Copy link

Choose a reason for hiding this comment

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

❓ Not sure why this module is called integration? To me it implies a module containing slow integration tests. We'd call it txkube.testtools in Flocker, right? What's your latest thinking :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's fast and local-only right now but I anticipate adding real-Kubernetes-interacting tests here once IKubernetesClient has expanded to support any such things.