dockercloud

This commit is contained in:
tifayuki 2016-01-14 13:12:51 +01:00
commit 2d9586a8a1
41 changed files with 2701 additions and 2 deletions

8
.gitignore vendored
View file

@ -2,8 +2,6 @@
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
@ -22,6 +20,7 @@ var/
*.egg-info/
.installed.cfg
*.egg
venv/
# PyInstaller
# Usually these files are written by a python script from a template
@ -55,3 +54,8 @@ docs/_build/
# PyBuilder
target/
# IDE
.idea/
venv/

13
Makefile Normal file
View file

@ -0,0 +1,13 @@
test:prepare
venv/bin/python setup.py test
clean:
rm -rf venv build dist *.egg-info
find . -name '*.pyc' -delete
prepare:clean
set -ex
virtualenv venv
venv/bin/pip install mock
venv/bin/pip install -r requirements.txt
venv/bin/python setup.py install

View file

@ -1,2 +1,80 @@
# python-docker-cloud
Python library for Docker Cloud
## Installation
In order to install the Docker Cloud Python library, you can use pip install:
pip install python-dockercloud
It will install a Python module called dockercloud which you can use to interface with the API.
## Authorization
The authentication can be configured in the following ways:
* Manually set it in your Python initialization code:
import dockercloud
dockercloud.user = "username"
dockercloud.apikey = "apikey"
* Login with docker cli, and the library will read the configfile automatically:
$ docker login
* Set the environment variables DOCKERCLOUD_USER and DOCKERCLOUD_APIKEY:
export DOCKERCLOUD_USER=username
export DOCKERCLOUD_APIKEY=apikey
## Errors
Errors in the HTTP API will be returned with status codes in the 4xx and 5xx ranges.
The Python library will detect this status codes and raise ApiError exceptions with the error message, which should be handled by the calling application accordingly.
## Quick examples
### Services
>>> import dockercloud
>>> dockercloud.Service.list()
[<dockercloud.api.service.Service object at 0x10701ca90>, <dockercloud.api.service.Service object at 0x10701ca91>]
>>> service = dockercloud.Service.fetch("fee900c6-97da-46b3-a21c-e2b50ed07015")
<dockercloud.api.service.Service object at 0x106c45c10>
>>> service.name
"my-python-app"
>>> service = dockercloud.Service.create(image="dockercloud/hello-world", name="my-new- app", target_num_containers=2)
>>> service.save()
True
>>> service.target_num_containers = 3
>>> service.save()
True
>>> service.stop()
True
>>> service.start()
True
>>> service.delete()
True
### Containers
>>> import dockercloud
>>> dockercloud.Container.list()
[<dockercloud.api.container.Container object at 0x10701ca90>, <dockercloud.api.container.Container object at 0x10701ca91>]
>>> container = dockercloud.Container.fetch("7d6696b7-fbaf-471d-8e6b-ce7052586c24")
<dockercloud.api.container.Container object at 0x10701ca90>
>>> container.public_dns = "my-web-app.example.com"
>>> container.save()
True
>>> container.stop()
True
>>> container.start()
True
>>> container.logs()
"2014-03-24 23:58:08,973 CRIT Supervisor running as root (no user in config file) [...]"
>>> container.delete()
True

49
dockercloud/__init__.py Normal file
View file

@ -0,0 +1,49 @@
import base64
import logging
import os
import requests
from future.standard_library import install_aliases
install_aliases()
from dockercloud.api import auth
from dockercloud.api.service import Service
from dockercloud.api.container import Container
from dockercloud.api.repository import Repository
from dockercloud.api.node import Node
from dockercloud.api.action import Action
from dockercloud.api.nodecluster import NodeCluster
from dockercloud.api.nodetype import NodeType
from dockercloud.api.nodeprovider import Provider
from dockercloud.api.noderegion import Region
from dockercloud.api.tag import Tag
from dockercloud.api.trigger import Trigger
from dockercloud.api.stack import Stack
from dockercloud.api.exceptions import ApiError, AuthError, ObjectNotFound, NonUniqueIdentifier
from dockercloud.api.utils import Utils
from dockercloud.api.events import Events
from dockercloud.api.nodeaz import AZ
__version__ = '1.0.0'
dockercloud_auth = os.environ.get('DOCKERCLOUD_AUTH')
basic_auth = auth.load_from_file("~/.docker/config.json")
if os.environ.get('DOCKERCLOUD_USER') and os.environ.get('DOCKERCLOUD_PASS'):
basic_auth = base64.b64encode("%s:%s" % (os.environ.get('DOCKERCLOUD_USER'), os.environ.get('DOCKERCLOUD_PASS')))
if os.environ.get('DOCKERCLOUD_USER') and os.environ.get('DOCKERCLOUD_APIKEY'):
basic_auth = base64.b64encode("%s:%s" % (os.environ.get('DOCKERCLOUD_USER'), os.environ.get('DOCKERCLOUD_APIKEY')))
rest_host = os.environ.get("DOCKERCLOUD_REST_HOST") or 'https://cloud.docker.com/'
stream_host = os.environ.get("DOCKERCLOUD_STREAM_HOST") or 'wss://ws.cloud.docker.com/'
user_agent = None
logging.basicConfig()
logger = logging.getLogger("python-dockercloud")
try:
requests.packages.urllib3.disable_warnings()
except:
pass

View file

23
dockercloud/api/action.py Normal file
View file

@ -0,0 +1,23 @@
from __future__ import absolute_import
from .base import Immutable, StreamingLog
class Action(Immutable):
subsystem = 'audit'
endpoint = "/action"
@classmethod
def _pk_key(cls):
return 'uuid'
def logs(self, tail, follow, log_handler=StreamingLog.default_log_handler):
logs = StreamingLog(self.subsystem, self.endpoint, self.pk, tail, follow)
logs.on_message(log_handler)
logs.run_forever()
def cancel(self):
return self._perform_action("cancel")
def retry(self):
return self._perform_action("retry")

67
dockercloud/api/auth.py Normal file
View file

@ -0,0 +1,67 @@
from __future__ import absolute_import
import base64
import json
import os
from requests.auth import HTTPBasicAuth
import dockercloud
from .http import send_request
def authenticate(username, password):
verify_credential(username, password)
dockercloud.basic_auth = base64.b64encode("%s:%s" % (username, password))
def verify_credential(username, password):
auth = HTTPBasicAuth(username, password)
send_request("GET", "/auth", auth=auth)
def is_authenticated():
try:
dockercloud.basic_auth = base64.b64encode("%s:%s" % (dockercloud.user, dockercloud.password))
except:
pass
try:
dockercloud.basic_auth = base64.b64encode("%s:%s" % (dockercloud.user, dockercloud.apikey))
except:
pass
return dockercloud.dockercloud_auth or dockercloud.basic_auth
def logout():
dockercloud.dockercloud_auth = None
dockercloud.basic_auth = None
def load_from_file(f="~/.docker/config.json"):
try:
with open(os.path.expanduser(f)) as config_file:
data = json.load(config_file)
return data.get("auths", {}).get("https://index.docker.io/v1/", {}).get("auth", None)
except Exception:
return None
def get_auth_header():
try:
dockercloud.basic_auth = base64.b64encode("%s:%s" % (dockercloud.user, dockercloud.password))
except:
pass
try:
dockercloud.basic_auth = base64.b64encode("%s:%s" % (dockercloud.user, dockercloud.apikey))
except:
pass
if dockercloud.dockercloud_auth:
return {'Authorization': dockercloud.dockercloud_auth}
if dockercloud.basic_auth:
return {'Authorization': 'Basic %s' % dockercloud.basic_auth}
return {}

351
dockercloud/api/base.py Normal file
View file

@ -0,0 +1,351 @@
from __future__ import absolute_import, print_function
import json as json_parser
import logging
import urllib
import websocket
import dockercloud
from .exceptions import ApiError, AuthError
from .http import send_request
logger = logging.getLogger("python-dockercloud")
class BasicObject(object):
_api_version = 'v1'
def __init__(self, **kwargs):
pass
class Restful(BasicObject):
_detail_uri = None
def __init__(self, **kwargs):
"""Simply reflect all the values in kwargs"""
for k, v in list(kwargs.items()):
setattr(self, k, v)
def __addchanges__(self, name):
changed_attrs = self.__getchanges__()
if not name in changed_attrs:
changed_attrs.append(name)
self.__setchanges__(changed_attrs)
def __setattr__(self, name, value):
"""Keeps track of what attributes have been set"""
current_value = getattr(self, name, None)
if value != current_value:
self.__addchanges__(name)
super(Restful, self).__setattr__(name, value)
def __getchanges__(self):
"""Internal. Convenience method to get the changed attrs list"""
return getattr(self, '__changedattrs__', [])
def __setchanges__(self, val):
"""Internal. Convenience method to set the changed attrs list"""
# Use the super implementation to prevent infinite recursion
super(Restful, self).__setattr__('__changedattrs__', val)
def _loaddict(self, dict):
"""Internal. Sets the model attributes to the dictionary values passed"""
endpoint = getattr(self, 'endpoint', None)
subsystem = getattr(self, 'subsystem', None)
assert endpoint, "Endpoint not specified for %s" % self.__class__.__name__
assert subsystem, "Subsystem not specified for %s" % self.__class__.__name__
for k, v in list(dict.items()):
setattr(self, k, v)
self._detail_uri = "/".join(["api", subsystem, self._api_version, endpoint.strip("/"), self.pk])
self.__setchanges__([])
@property
def pk(self):
"""Returns the primary key for the object.
:returns: str -- the primary key for the object
"""
return getattr(self, self._pk_key(), None)
@classmethod
def _pk_key(cls):
"""Internal. Returns the attribute name that acts as primary key of the model. Can be overridden by subclasses.
:returns: str -- the name of the primary key attribute for the model
"""
return 'uuid'
@property
def is_dirty(self):
"""Returns whether or not the object has unsaved changes
:returns: bool -- whether or not the object has unsaved changes
"""
return len(self.__getchanges__()) > 0
def _perform_action(self, action, params=None, data={}):
"""Internal. Performs the specified action on the object remotely"""
success = False
if not self._detail_uri:
raise ApiError("You must save the object before performing this operation")
path = "/".join([self._detail_uri.rstrip("/"), action.lstrip("/")])
json = send_request("POST", path, params=params, data=data)
if json:
self._loaddict(json)
success = True
return success
def _expand_attribute(self, attribute):
"""Internal. Expands the given attribute from remote information"""
if not self._detail_uri:
raise ApiError("You must save the object before performing this operation")
path = "/".join([self._detail_uri, attribute])
json = send_request("GET", path)
if json:
return json[attribute]
return None
def get_all_attributes(self):
"""Returns a dict with all object attributes
:returns: dict -- all object attributes as a dict
"""
attributes = {}
for attr in [attr for attr in vars(self) if not attr.startswith('_')]:
attributes[attr] = getattr(self, attr, None)
return attributes
class Immutable(Restful):
@classmethod
def fetch(cls, pk):
instance = None
endpoint = getattr(cls, 'endpoint', None)
subsystem = getattr(cls, 'subsystem', None)
assert endpoint, "Endpoint not specified for %s" % cls.__name__
assert subsystem, "Subsystem not specified for %s" % cls.__name__
detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/"), pk])
json = send_request('GET', detail_uri)
if json:
instance = cls()
instance._loaddict(json)
return instance
@classmethod
def list(cls, limit=None, **kwargs):
restful = []
endpoint = getattr(cls, 'endpoint', None)
subsystem = getattr(cls, 'subsystem', None)
assert endpoint, "Endpoint not specified for %s" % cls.__name__
assert subsystem, "Subsystem not specified for %s" % cls.__name__
detail_uri = "/".join(["api", subsystem, cls._api_version, endpoint.strip("/")])
objects = []
while True:
if limit and len(objects) >= limit:
break
json = send_request('GET', detail_uri, params=kwargs)
objs = json.get('objects', [])
meta = json.get('meta', {})
next_url = meta.get('next', '')
offset = meta.get('offset', 0)
api_limit = meta.get('limit', 0)
objects.extend(objs)
if next_url:
kwargs['offset'] = offset + api_limit
kwargs['limit'] = api_limit
else:
break
if limit:
objects = objects[:limit]
for obj in objects:
instance = cls()
instance._loaddict(obj)
restful.append(instance)
return restful
def refresh(self, force=False):
success = False
if self.is_dirty and not force:
# We have local non-committed changes - rejecting the refresh
success = False
elif not self._detail_uri:
raise ApiError("You must save the object before performing this operation")
else:
json = send_request("GET", self._detail_uri)
if json:
self._loaddict(json)
success = True
return success
class Mutable(Immutable):
@classmethod
def create(cls, **kwargs):
"""Returns a new instance of the model (without saving it) with the attributes specified in ``kwargs``
:returns: RESTModel -- a new local instance of the model
"""
return cls(**kwargs)
def delete(self):
if not self._detail_uri:
raise ApiError("You must save the object before performing this operation")
action = "DELETE"
url = self._detail_uri
json = send_request(action, url)
if json:
self._loaddict(json)
else:
# Object deleted successfully and nothing came back - deleting PK reference.
self._detail_uri = None
# setattr(self, self._pk_key(), None) -- doesn't work
self.__setchanges__([])
return True
def save(self):
success = False
if not self.is_dirty:
# No changes
success = True
else:
cls = self.__class__
endpoint = getattr(cls, 'endpoint', None)
subsystem = getattr(cls, 'subsystem', None)
assert endpoint, "Endpoint not specified for %s" % self.__class__.__name__
assert subsystem, "Subsystem not specified for %s" % self.__class__.__name__
# Figure out whether we should do a create or update
if not self._detail_uri:
action = "POST"
path = "/".join(["api", subsystem, self._api_version, endpoint.lstrip("/")])
else:
action = "PATCH"
path = self._detail_uri
# Construct the necessary params
params = {}
for attr in self.__getchanges__():
value = getattr(self, attr, None)
params[attr] = value
# Construct the json body
payload = None
if params:
payload = json_parser.dumps(params)
if not payload:
payload = json_parser.dumps({})
# Make the request
success = False
json = send_request(action, path, data=payload)
if json:
self._loaddict(json)
success = True
return success
class Taggable(BasicObject):
pass
class Triggerable(BasicObject):
pass
class StreamingAPI(BasicObject):
def __init__(self, url):
self._ws_init(url)
def _ws_init(self, url):
self.url = url
user_agent = 'python-dockercloud/%s' % dockercloud.__version__
if dockercloud.user_agent:
user_agent = "%s %s" % (dockercloud.user_agent, user_agent)
header = {'User-Agent': user_agent}
header.update(dockercloud.auth.get_auth_header())
self.header = [": ".join([key, value]) for key, value in header.items()]
logger.info("websocket: %s %s" % (self.url, self.header))
self.open_handler = None
self.message_handler = None
self.error_handler = None
self.close_handler = None
self.auth_error = False
def _on_open(self, ws):
if self.open_handler:
self.open_handler()
def _on_message(self, ws, message):
if self.message_handler:
self.message_handler(message)
def _on_error(self, ws, error):
if self.error_handler:
self.error_handler(error)
def _on_close(self, ws):
if self.close_handler:
self.close_handler()
def on_open(self, handler):
self.open_handler = handler
def on_message(self, handler):
self.message_handler = handler
def on_error(self, handler):
self.error_handler = handler
def on_close(self, handler):
self.close_handler = handler
def run_forever(self, *args, **kwargs):
while True:
if getattr(self, "auth_error", False):
raise AuthError("Not authorized")
ws = websocket.WebSocketApp(self.url, header=self.header,
on_open=self._on_open,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close)
ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs)
class StreamingLog(StreamingAPI):
def __init__(self, subsystem, resource, uuid, tail, follow):
endpoint = "%s/%s/logs/?follow=%s" % (resource, uuid, str(follow).lower())
if tail:
endpoint = "%s&tail=%d" % (endpoint, tail)
url = "/".join([dockercloud.stream_host.rstrip("/"), "api", subsystem, self._api_version, endpoint.lstrip("/")])
super(self.__class__, self).__init__(url)
@staticmethod
def default_log_handler(message):
print(message)
def run_forever(self, *args, **kwargs):
ws = websocket.WebSocketApp(self.url, header=self.header,
on_open=self._on_open,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close)
ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs)
class Exec(StreamingAPI):
def __init__(self, uuid, cmd='sh'):
endpoint = "container/%s/exec/?command=%s" % (uuid, urllib.quote_plus(cmd))
url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "app", self._api_version, endpoint.lstrip("/")])
super(self.__class__, self).__init__(url)
@staticmethod
def default_message_handler(message):
print(message)
def run_forever(self, *args, **kwargs):
ws = websocket.WebSocketApp(self.url, header=self.header,
on_open=self._on_open,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close)
ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs)

View file

@ -0,0 +1,33 @@
from __future__ import absolute_import
from .base import Mutable, StreamingLog, Exec
class Container(Mutable):
subsystem = "app"
endpoint = "/container"
def save(self):
raise AttributeError("'save' is not supported in 'Container' object. "
"Please use the related 'Service' object instead.")
def start(self):
return self._perform_action("start")
def stop(self):
return self._perform_action("stop")
def redeploy(self, reuse_volumes=True):
params = {'reuse_volumes': reuse_volumes}
return self._perform_action("redeploy", params=params)
def logs(self, tail, follow, log_handler=StreamingLog.default_log_handler):
logs = StreamingLog(self.subsystem, self.endpoint, self.pk, tail, follow)
logs.on_message(log_handler)
logs.run_forever()
def execute(self, cmd, handler=Exec.default_message_handler):
if hasattr(self, "uuid"):
exec_obj = Exec(self.uuid, cmd)
exec_obj.on_message(handler)
exec_obj.run_forever()

42
dockercloud/api/events.py Normal file
View file

@ -0,0 +1,42 @@
from __future__ import absolute_import
import json
import websocket
import dockercloud
from .base import StreamingAPI
from .exceptions import AuthError
class Events(StreamingAPI):
def __init__(self):
endpoint = "events"
url = "/".join([dockercloud.stream_host.rstrip("/"), "api", "audit", self._api_version, endpoint.lstrip("/")])
super(self.__class__, self).__init__(url)
def _on_message(self, ws, message):
try:
event = json.loads(message)
except ValueError:
return
if event.get("type") == "error" and event.get("data", {}).get("errorMessage") == "UNAUTHORIZED":
self.auth_error = True
raise AuthError("Not authorized")
if event.get("type") == "auth":
return
if self.message_handler:
self.message_handler(event)
def run_forever(self, *args, **kwargs):
while True:
if self.auth_error:
raise AuthError("Not authorized")
ws = websocket.WebSocketApp(self.url, header=self.header,
on_open=self._on_open,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close)
ws.run_forever(ping_interval=5, ping_timeout=5, *args, **kwargs)

View file

@ -0,0 +1,16 @@
class ApiError(Exception):
"""An error status code was returned when querying the HTTP API"""
pass
class AuthError(ApiError):
"""An 401 Unauthorized status code was returned when querying the API"""
pass
class NonUniqueIdentifier(ApiError):
pass
class ObjectNotFound(ApiError):
pass

64
dockercloud/api/http.py Normal file
View file

@ -0,0 +1,64 @@
from __future__ import absolute_import
import logging
from requests import Request, Session
from requests import utils
from urllib.parse import urljoin
import dockercloud
from .exceptions import ApiError, AuthError
logger = logging.getLogger("python-dockercloud")
def send_request(method, path, inject_header=True, **kwargs):
json = None
url = urljoin(dockercloud.rest_host.rstrip("/"), path.strip("/").encode("ascii", "ignore"))
if not url.endswith("/"):
url = "%s/" % url
user_agent = 'python-dockercloud/%s' % dockercloud.__version__
if dockercloud.user_agent:
user_agent = "%s %s" % (dockercloud.user_agent, user_agent)
# construct headers
headers = {'Content-Type': 'application/json', 'User-Agent': user_agent}
headers.update(dockercloud.auth.get_auth_header())
logger.info("Request: %s, %s, %s, %s" % (method, url, headers, kwargs))
# construct request
s = Session()
req = Request(method, url, headers=headers, **kwargs)
# get environment proxies
env_proxies = utils.get_environ_proxies(url) or {}
kw_args = {'proxies': env_proxies}
# make the request
response = s.send(req.prepare(), **kw_args)
status_code = getattr(response, 'status_code', None)
logger.info("Response: Status %s, %s, %s" % (str(status_code), response.headers, response.text))
# handle the response
if not status_code:
# Most likely network trouble
raise ApiError("No Response (%s %s)" % (method, url))
elif 200 <= status_code <= 299:
# Success
if status_code != 204:
# Try to parse the response.
try:
json = response.json()
if response.headers and inject_header:
json["dockercloud_action_uri"] = response.headers.get("X-DockerCloud-Action-URI", "")
except TypeError:
raise ApiError("JSON Parse Error (%s %s). Response: %s" % (method, url, response.text))
else:
json = None
else:
# Server returned an error
if status_code == 401:
raise AuthError("Not authorized")
else:
raise ApiError("Status %s (%s %s). Response: %s" % (str(status_code), method, url, response.text))
return json

19
dockercloud/api/node.py Normal file
View file

@ -0,0 +1,19 @@
from __future__ import absolute_import
from .base import Mutable, Taggable
class Node(Mutable, Taggable):
subsystem = "infra"
endpoint = "/node"
def save(self):
if not self._detail_uri:
raise AttributeError("Adding a new node is not supported via 'save' method")
super(Node, self).save()
def deploy(self, tag=None):
return self._perform_action("deploy")
def upgrade_docker(self):
return self._perform_action("docker-upgrade")

12
dockercloud/api/nodeaz.py Normal file
View file

@ -0,0 +1,12 @@
from __future__ import absolute_import
from .base import Immutable
class AZ(Immutable):
subsystem = "infra"
endpoint = "/az"
@classmethod
def _pk_key(cls):
return 'name'

View file

@ -0,0 +1,22 @@
from __future__ import absolute_import
from .base import Mutable, Taggable
from .noderegion import Region
from .nodetype import NodeType
class NodeCluster(Mutable, Taggable):
subsystem = "infra"
endpoint = "/nodecluster"
def deploy(self, tag=None):
return self._perform_action("deploy")
@classmethod
def create(cls, **kwargs):
for key, value in kwargs.items():
if key == "node_type" and isinstance(value, NodeType):
kwargs[key] = getattr(value, "resource_uri", "")
if key == "region" and isinstance(value, Region):
kwargs[key] = getattr(value, "resource_uri", "")
return cls(**kwargs)

View file

@ -0,0 +1,18 @@
from __future__ import absolute_import
from .base import Immutable
class Provider(Immutable):
subsystem = "infra"
endpoint = "/provider"
@classmethod
def _pk_key(cls):
return 'name'
def delete(self):
raise AttributeError("'delete' is not supported in 'Provider'")
def save(self):
raise AttributeError("'save' is not supported in 'Provider'")

View file

@ -0,0 +1,12 @@
from __future__ import absolute_import
from .base import Immutable
class Region(Immutable):
subsystem = "infra"
endpoint = "/region"
@classmethod
def _pk_key(cls):
return 'name'

View file

@ -0,0 +1,12 @@
from __future__ import absolute_import
from .base import Immutable
class NodeType(Immutable):
subsystem = "infra"
endpoint = "/nodetype"
@classmethod
def _pk_key(cls):
return 'name'

View file

@ -0,0 +1,12 @@
from __future__ import absolute_import
from .base import Mutable, Taggable
class Repository(Mutable, Taggable):
subsystem = "repo"
endpoint = "/repository"
@classmethod
def _pk_key(cls):
return 'name'

View file

@ -0,0 +1,26 @@
from __future__ import absolute_import
from .base import Mutable, Taggable, Triggerable, StreamingLog
class Service(Mutable, Taggable, Triggerable):
subsystem = "app"
endpoint = "/service"
def start(self):
return self._perform_action("start")
def stop(self):
return self._perform_action("stop")
def redeploy(self, reuse_volumes=True):
params = {'reuse_volumes': reuse_volumes}
return self._perform_action("redeploy", params=params)
def scale(self):
return self._perform_action("scale")
def logs(self, tail, follow, log_handler=StreamingLog.default_log_handler):
logs = StreamingLog(self.subsystem, self.endpoint, self.pk, tail, follow)
logs.on_message(log_handler)
logs.run_forever()

26
dockercloud/api/stack.py Normal file
View file

@ -0,0 +1,26 @@
from __future__ import absolute_import
from .base import Mutable
from .exceptions import ApiError
from .http import send_request
class Stack(Mutable):
subsystem = "app"
endpoint = "/stack"
def start(self):
return self._perform_action("start")
def stop(self):
return self._perform_action("stop")
def redeploy(self, reuse_volumes=True):
params = {'reuse_volumes': reuse_volumes}
return self._perform_action("redeploy", params=params)
def export(self):
if not self._detail_uri:
raise ApiError("You must save the object before performing this operation")
url = "/".join([self._detail_uri, "export"])
return send_request("GET", url, inject_header=False)

78
dockercloud/api/tag.py Normal file
View file

@ -0,0 +1,78 @@
from __future__ import absolute_import
from .base import Taggable, BasicObject
from .exceptions import ApiError
class Tag(BasicObject):
def __init__(self):
self.tags = []
def add(self, tagname):
if isinstance(tagname, list):
for t in tagname:
self.taggable.tags.append({"name": t})
else:
self.taggable.tags.append({"name": tagname})
self.taggable.__addchanges__('tags')
@classmethod
def create(cls, **kwargs):
return cls(**kwargs)
def remove(self, tagname):
if not self.taggable:
raise ApiError("You must initialize the tag object before performing this operation")
_tags = []
tagnames = []
if isinstance(tagname, list):
for n in tagname:
tagnames.append(n)
else:
tagnames.append(tagname)
for t in self.taggable.tags:
for tagname in tagnames:
if t.get("name", "") == tagname:
_tags.append(t)
if _tags:
for _tag in _tags:
self.taggable.tags.remove(_tag)
self.taggable.__addchanges__('tags')
def delete(self, tagname):
if not self.taggable:
raise ApiError("You must initialize the tag object before performing this operation")
if self.taggable.is_dirty:
raise ApiError("You must save the tab object before performing this operation")
self.remove(tagname)
return self.save()
@classmethod
def fetch(cls, taggable):
if not isinstance(taggable, Taggable):
raise ApiError("The object does not support tag")
if not taggable._detail_uri:
raise ApiError("You must save the taggable object before performing this operation")
tag = cls()
tag.taggable = taggable
return tag
def list(self, **kwargs):
if not self.taggable:
raise ApiError("You must initialize the tag object before performing this operation")
return self.taggable.tags
def save(self):
if not self.taggable:
raise ApiError("You must initialize the tag object before performing this operation")
return self.taggable.save()

102
dockercloud/api/trigger.py Normal file
View file

@ -0,0 +1,102 @@
import json as json_parser
from .base import Triggerable, BasicObject
from .exceptions import ApiError
from .http import send_request
class Trigger(BasicObject):
def __init__(self):
self.trigger = None
def add(self, name=None, operation=None):
if self.trigger is not None:
raise ApiError("You must save the object before performing this operation")
trigger = {}
if name:
trigger['name'] = name
if operation:
trigger['operation'] = operation
self.trigger = trigger
@classmethod
def create(cls, **kwargs):
"""Returns a new instance of the model (without saving it) with the attributes specified in ``kwargs``
:returns: trigger -- a new local instance of the Trigger
"""
return cls(**kwargs)
def delete(self, uuid):
if not self.endpoint:
raise ApiError("You must initialize the Trigger object before performing this operation")
action = "DELETE"
url = "/".join([self.endpoint, uuid])
send_request(action, url)
return True
@classmethod
def fetch(cls, triggerable):
if not isinstance(triggerable, Triggerable):
raise ApiError("The object does not support trigger")
if not triggerable._detail_uri:
raise ApiError("You must save the triggerable object before performing this operation")
trigger = cls()
trigger.endpoint = "/".join([triggerable._detail_uri, "trigger"])
handlers = []
for t in trigger.list():
triggername = t.get("name", "")
if triggername:
handlers.append({"name": triggername})
return trigger
def list(self, **kwargs):
if not self.endpoint:
raise ApiError("You must initialize the Trigger object before performing this operation")
objects = []
while True:
json = send_request('GET', self.endpoint, params=kwargs)
objs = json.get('objects', [])
meta = json.get('meta', {})
next_url = meta.get('next', '')
offset = meta.get('offset', 0)
limit = meta.get('limit', 0)
objects.extend(objs)
if next_url:
kwargs['offset'] = offset + limit
kwargs['limit'] = limit
else:
break
return objects
def save(self):
if not self.endpoint:
raise ApiError("You must initialize the Trigger object before performing this operation")
if self.trigger is None:
return True
json = send_request("POST", self.endpoint, data=json_parser.dumps(self.trigger))
if json:
self.clear()
self.clear()
return True
def call(self, uuid):
if not self.endpoint:
raise ApiError("You must initialize the Trigger object before performing this operation")
json = send_request("POST", "/".join([self.endpoint, uuid + "/call"]))
if json:
return True
return False
def clear(self):
self.trigger = None

193
dockercloud/api/utils.py Normal file
View file

@ -0,0 +1,193 @@
from __future__ import absolute_import
import re
from .action import Action
from .container import Container
from .exceptions import ApiError, ObjectNotFound, NonUniqueIdentifier
from .node import Node
from .nodecluster import NodeCluster
from .service import Service
from .stack import Stack
def is_uuid4(identifier):
uuid4_regexp = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}', re.I)
match = uuid4_regexp.match(identifier)
return bool(match)
class Utils:
@staticmethod
def fetch_by_resource_uri(uri):
if not isinstance(uri, basestring):
raise ApiError("Uri format is invalid")
terms = uri.strip("/").split("/")
if len(terms) < 2:
raise ApiError("Uri format is invalid")
id = terms[-1]
resource_type = terms[-2]
if resource_type.lower() == "container":
return Container.fetch(id)
elif resource_type.lower() == "service":
return Service.fetch(id)
elif resource_type.lower() == "stack":
return Stack.fetch(id)
elif resource_type.lower() == "node":
return Node.fetch(id)
elif resource_type.lower() == "nodecluster":
return NodeCluster.fetch(id)
elif resource_type.lower() == "action":
return Action.fetch(id)
else:
raise ApiError(
"Unsupported resource type. Only support: action, container, node, nodecluster, service, stack")
@staticmethod
def fetch_remote_container(identifier, raise_exceptions=True):
try:
if is_uuid4(identifier):
try:
return Container.fetch(identifier)
except Exception:
raise ObjectNotFound("Cannot find a container with the identifier '%s'" % identifier)
else:
if "." in identifier:
terms = identifier.split(".", 2)
objects_same_identifier = Container.list(name=terms[0], service__stack__name=terms[1])
else:
objects_same_identifier = Container.list(uuid__startswith=identifier) or \
Container.list(name=identifier)
if len(objects_same_identifier) == 1:
uuid = objects_same_identifier[0].uuid
return Container.fetch(uuid)
elif len(objects_same_identifier) == 0:
raise ObjectNotFound("Cannot find a container with the identifier '%s'" % identifier)
raise NonUniqueIdentifier("More than one container has the same identifier, please use the long uuid")
except (NonUniqueIdentifier, ObjectNotFound) as e:
if not raise_exceptions:
return e
raise e
@staticmethod
def fetch_remote_service(identifier, raise_exceptions=True):
try:
if is_uuid4(identifier):
try:
return Service.fetch(identifier)
except Exception:
raise ObjectNotFound("Cannot find a service with the identifier '%s'" % identifier)
else:
if "." in identifier:
terms = identifier.split(".", 2)
objects_same_identifier = Service.list(name=terms[0], stack__name=terms[1])
else:
objects_same_identifier = Service.list(uuid__startswith=identifier) or \
Service.list(name=identifier)
if len(objects_same_identifier) == 1:
uuid = objects_same_identifier[0].uuid
return Service.fetch(uuid)
elif len(objects_same_identifier) == 0:
raise ObjectNotFound("Cannot find a service with the identifier '%s'" % identifier)
raise NonUniqueIdentifier("More than one service has the same identifier, please use the long uuid")
except (NonUniqueIdentifier, ObjectNotFound) as e:
if not raise_exceptions:
return e
raise e
@staticmethod
def fetch_remote_stack(identifier, raise_exceptions=True):
try:
if is_uuid4(identifier):
try:
return Stack.fetch(identifier)
except Exception:
raise ObjectNotFound("Cannot find a stack with the identifier '%s'" % identifier)
else:
objects_same_identifier = Stack.list(uuid__startswith=identifier) or \
Stack.list(name=identifier)
if len(objects_same_identifier) == 1:
uuid = objects_same_identifier[0].uuid
return Stack.fetch(uuid)
elif len(objects_same_identifier) == 0:
raise ObjectNotFound("Cannot find a stack with the identifier '%s'" % identifier)
raise NonUniqueIdentifier("More than one stack has the same identifier, please use the long uuid")
except (NonUniqueIdentifier, ObjectNotFound) as e:
if not raise_exceptions:
return e
raise e
@staticmethod
def fetch_remote_node(identifier, raise_exceptions=True):
try:
if is_uuid4(identifier):
try:
return Node.fetch(identifier)
except Exception:
raise ObjectNotFound("Cannot find a node with the identifier '%s'" % identifier)
else:
objects_same_identifier = Node.list(uuid__startswith=identifier)
if len(objects_same_identifier) == 1:
uuid = objects_same_identifier[0].uuid
return Node.fetch(uuid)
elif len(objects_same_identifier) == 0:
raise ObjectNotFound("Cannot find a node with the identifier '%s'" % identifier)
raise NonUniqueIdentifier("More than one node has the same identifier, please use the long uuid")
except (NonUniqueIdentifier, ObjectNotFound) as e:
if not raise_exceptions:
return e
raise e
@staticmethod
def fetch_remote_nodecluster(identifier, raise_exceptions=True):
try:
if is_uuid4(identifier):
try:
return NodeCluster.fetch(identifier)
except Exception:
raise ObjectNotFound("Cannot find a node cluster with the identifier '%s'" % identifier)
else:
objects_same_identifier = NodeCluster.list(uuid__startswith=identifier) or \
NodeCluster.list(name=identifier)
if len(objects_same_identifier) == 1:
uuid = objects_same_identifier[0].uuid
return NodeCluster.fetch(uuid)
elif len(objects_same_identifier) == 0:
raise ObjectNotFound("Cannot find a node cluster with the identifier '%s'" % identifier)
raise NonUniqueIdentifier(
"More than one node cluster has the same identifier, please use the long uuid")
except (NonUniqueIdentifier, ObjectNotFound) as e:
if not raise_exceptions:
return e
raise e
@staticmethod
def fetch_remote_action(identifier, raise_exceptions=True):
try:
if is_uuid4(identifier):
try:
return Action.fetch(identifier)
except Exception:
raise ObjectNotFound("Cannot find an action with the identifier '%s'" % identifier)
else:
objects_same_identifier = Action.list(uuid__startswith=identifier)
if len(objects_same_identifier) == 1:
uuid = objects_same_identifier[0].uuid
return Action.fetch(uuid)
elif len(objects_same_identifier) == 0:
raise ObjectNotFound("Cannot find an action cluster with the identifier '%s'" % identifier)
raise NonUniqueIdentifier(
"More than one action has the same identifier, please use the long uuid")
except (NonUniqueIdentifier, ObjectNotFound) as e:
if not raise_exceptions:
return e
raise e

5
requirements.txt Normal file
View file

@ -0,0 +1,5 @@
backports.ssl-match-hostname==3.4.0.2
future==0.15.0
requests==2.7.0
six==1.9.0
websocket-client==0.32.0

40
setup.py Normal file
View file

@ -0,0 +1,40 @@
import codecs
import os
import re
from setuptools import setup, find_packages
def read(*parts):
path = os.path.join(os.path.dirname(__file__), *parts)
with codecs.open(path, encoding='utf-8') as fobj:
return fobj.read()
def find_version(*file_paths):
version_file = read(*file_paths)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError('Unable to find version string.')
with open('requirements.txt') as f:
install_requires = f.read().splitlines()
setup(
name="python-dockercloud",
version=find_version('dockercloud', '__init__.py'),
packages=find_packages(),
install_requires=install_requires,
provides=['docker'],
include_package_data=True,
author="Docker, Inc.",
author_email="info@docker.com",
description="Python Library for Dockercloud",
license="Apache v2",
keywords="docker cloud",
url="http://cloud.docker.com/",
test_suite="tests",
)

12
tests/__init__.py Normal file
View file

@ -0,0 +1,12 @@
__author__ = 'fermayo'
# Python 3.4.2 includes mock in-box, prefer that version
# For other versions, patch it up to use the external mock library
try:
from unittest import mock
except ImportError:
import sys
sys.modules['unittest'] = __import__('unittest')
sys.modules['unittest.mock'] = __import__('mock')
setattr(sys.modules['unittest'], 'mock', __import__('mock'))

276
tests/fake_api.py Normal file
View file

@ -0,0 +1,276 @@
import datetime
import json
import requests
FAKE_USER = 'fake_user'
FAKE_PASSWORD = 'fake_password'
FAKE_DOCKERCLOUD_AUTH = 'fake_auth'
FAKE_BASIC_AUTH = 'ZmFrZV91c2VyOmZha2VfcGFzc3dvcmQ='
FAKE_APIKEY_AUTH = 'ZmFrZV90dXR1bV91c2VyOmZha2VfdHV0dW1fYXBpa2V5'
FAKE_APIKEY = 'fake_apikey'
FAKE_EMAIL = 'fake@docker.com'
FAKE_UUID = 'b0374cc2-4003-4270-b131-25fc494ea2be'
FAKE_UUIDS = ['b0374cc2-4003-4270-b131-25fc494ea2be', 'd89fc6f9-d7ec-4602-be94-429c65d6657d',
'aeaa0b9f-a878-488a-b4a5-a5b54264edd7']
def response(status_code=200, content='', headers=None, reason=None, elapsed=0,
request=None):
res = requests.Response()
res.status_code = status_code
content = json.dumps(content).encode('ascii')
res._content = content
res.headers = requests.structures.CaseInsensitiveDict(headers or {})
res.reason = reason
res.elapsed = datetime.timedelta(elapsed)
res.request = request
return res
def fake_resp(fake_api_call):
status_code, content = fake_api_call()
return response(status_code=status_code, content=content)
def fake_auth():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 1},' \
'"objects": [{"key": "%s", "username": "%s"}]}' % (FAKE_APIKEY, FAKE_USER)
return status_code, json.loads(resp)
def fake_action_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 3}, ' \
'"objects": [{"action": "Node Cluster Create", "end_date": "Mon, 29 Sep 2014 15:40:59 +0000", "ip": "207.41.188.212", "location": "New York, United States", "method": "POST", "object": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "path": "/api/v1/nodecluster/", "resource_uri": "/api/v1/action/7f62b667-2693-420a-ad2e-41cda5605322/", "start_date": "Mon, 29 Sep 2014 15:40:59 +0000", "state": "Success", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "7f62b667-2693-420a-ad2e-41cda5605322"}, ' \
'{"action": "Node Cluster Deploy", "end_date": "Mon, 29 Sep 2014 15:41:01 +0000", "ip": "207.41.188.212", "location": "New York, United States", "method": "POST", "object": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "path": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/deploy/", "resource_uri": "/api/v1/action/db69b048-3bab-4a2e-bcbd-91265edf1a31/", "start_date": "Mon, 29 Sep 2014 15:41:00 +0000", "state": "Failed", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "db69b048-3bab-4a2e-bcbd-91265edf1a31"}, ' \
'{"action": "Node Deploy", "end_date": "Mon, 29 Sep 2014 15:41:16 +0000", "ip": "207.41.188.212", "location": "New York, United States", "method": "POST", "object": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "path": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/deploy/", "resource_uri": "/api/v1/action/ce9ae16b-88fa-4be6-b12e-fc970b8d2445/", "start_date": "Mon, 29 Sep 2014 15:41:16 +0000", "state": "Failed", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "ce9ae16b-88fa-4be6-b12e-fc970b8d2445"}]}'
return status_code, json.loads(resp)
def fake_action_fetch():
status_code = 200
resp = '{"action": "Node Cluster Create", "end_date": "Mon, 29 Sep 2014 15:40:59 +0000", "ip": "207.41.188.212", "location": "New York, United States", "logs": "", "method": "POST", "object": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "path": "/api/v1/nodecluster/", "resource_uri": "/api/v1/action/7f62b667-2693-420a-ad2e-41cda5605322/", "start_date": "Mon, 29 Sep 2014 15:40:59 +0000", "state": "Success", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "7f62b667-2693-420a-ad2e-41cda5605322"}'
return status_code, json.loads(resp)
def fake_provider_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 1}, "objects": [{"available": true, "label": "Digital Ocean", "name": "digitalocean", "regions": ["/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/ams3/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/sgp1/"], "resource_uri": "/api/v1/provider/digitalocean/"}]}'
return status_code, json.loads(resp)
def fake_provider_fetch():
status_code = 200
resp = '{"available": true, "label": "Digital Ocean", "name": "digitalocean", "regions": ["/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/ams3/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/sgp1/"], "resource_uri": "/api/v1/provider/digitalocean/"}'
return status_code, json.loads(resp)
def fake_region_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 8}, ' \
'"objects": [{"availability_zones": [], "available": true, "label": "Amsterdam 1", "name": "ams1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/"], "resource_uri": "/api/v1/region/digitalocean/ams1/"}, ' \
'{"availability_zones": [], "available": true, "label": "San Francisco 1", "name": "sfo1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/sfo1/"}, ' \
'{"availability_zones": [], "available": true, "label": "New York 2", "name": "nyc2", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/nyc2/"}, ' \
'{"availability_zones": [], "available": true, "label": "Amsterdam 2", "name": "ams2", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/ams2/"}, ' \
'{"availability_zones": [], "available": true, "label": "Singapore 1", "name": "sgp1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/sgp1/"}, ' \
'{"availability_zones": [], "available": true, "label": "London 1", "name": "lon1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/lon1/"}, ' \
'{"availability_zones": [], "available": true, "label": "New York 3", "name": "nyc3", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/nyc3/"}, ' \
'{"availability_zones": [], "available": true, "label": "Amsterdam 3", "name": "ams3", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/ams3/"}]}'
return status_code, json.loads(resp)
def fake_region_fetch():
status_code = 200
resp = '{"availability_zones": [], "available": true, "label": "Amsterdam 1", "name": "ams1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/"], "provider": "/api/v1/provider/digitalocean/", "resource_uri": "/api/v1/region/digitalocean/ams1/"}'
return status_code, json.loads(resp)
def fake_nodetype_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 9}, ' \
'"objects": [{"availability_zones": [], "available": true, "label": "512MB", "name": "512mb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/512mb/"}, ' \
'{"availability_zones": [], "available": true, "label": "1GB", "name": "1gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/1gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "2GB", "name": "2gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/2gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "4GB", "name": "4gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/4gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "8GB", "name": "8gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/8gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "16GB", "name": "16gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/16gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "32GB", "name": "32gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/32gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "48GB", "name": "48gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/48gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "64GB", "name": "64gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/64gb/"}]}'
return status_code, json.loads(resp)
def fake_nodetype_fetch():
status_code = 200
resp = '{"availability_zones": [], "available": true, "label": "8GB", "name": "8gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/8gb/"}'
return status_code, json.loads(resp)
def fake_nodeclster_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 2}, ' \
'"objects": [{"current_num_nodes": 1, "deployed_datetime": "Mon, 29 Sep 2014 22:29:03 +0000", "destroyed_datetime": null, "name": "test", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "region": "/api/v1/region/digitalocean/sfo1/", "resource_uri": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "state": "Deployed", "target_num_nodes": 1, "uuid": "a02c3763-e639-46fc-a6db-587f4dbb5444"}, ' \
'{"current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "test2", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "state": "Deploying", "target_num_nodes": 1, "uuid": "b616a720-6684-42c6-83bb-4d298b11b3f3"}]}'
return status_code, json.loads(resp)
def fake_nodecluster_fetch():
status_code = 200
resp = '{"actions": ["/api/v1/action/bf02b00a-e2fc-4098-8b69-1424b659ef4a/", "/api/v1/action/f8dce6d4-5c41-46a9-9754-baa8f3cdf031/"], "current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "test2", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "nodes": ["/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "state": "Init", "target_num_nodes": 1, "uuid": "b616a720-6684-42c6-83bb-4d298b11b3f3"}'
return status_code, json.loads(resp)
def fake_nodecluster_save():
status_code = 201
resp = '{"actions": ["/api/v1/action/f47e26a6-c60c-416f-a0a9-ddf14e3aae83/"], "current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "my_cluster", "node_type": "/api/v1/nodetype/digitalocean/1gb/", "nodes": ["/api/v1/node/2cfe7823-f551-4c7b-a82c-f6ab31d7ca25/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/e7915a74-618b-4908-9189-dce965465702/", "state": "Init", "target_num_nodes": 1, "uuid": "e7915a74-618b-4908-9189-dce965465702"}'
return status_code, json.loads(resp)
def fake_nodecluster_deploy():
status_code = 202
resp = '{"actions": ["/api/v1/action/f47e26a6-c60c-416f-a0a9-ddf14e3aae83/", "/api/v1/action/d110016e-e65d-4ce7-9f11-50c6302494a6/"], "current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "my_cluster", "node_type": "/api/v1/nodetype/digitalocean/1gb/", "nodes": ["/api/v1/node/2cfe7823-f551-4c7b-a82c-f6ab31d7ca25/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/e7915a74-618b-4908-9189-dce965465702/", "state": "Deploying", "target_num_nodes": 1, "uuid": "e7915a74-618b-4908-9189-dce965465702"}'
return status_code, json.loads(resp)
def fake_nodecluster_delete():
status_code = 202
resp = '{"actions": ["/api/v1/action/f47e26a6-c60c-416f-a0a9-ddf14e3aae83/", "/api/v1/action/d110016e-e65d-4ce7-9f11-50c6302494a6/", "/api/v1/action/e33b4bb1-192b-46a6-a1ba-eadfc494c2dd/"], "current_num_nodes": 1, "deployed_datetime": "Mon, 29 Sep 2014 23:45:45 +0000", "destroyed_datetime": null, "name": "my_cluster", "node_type": "/api/v1/nodetype/digitalocean/1gb/", "nodes": ["/api/v1/node/2cfe7823-f551-4c7b-a82c-f6ab31d7ca25/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/e7915a74-618b-4908-9189-dce965465702/", "state": "Terminating", "target_num_nodes": 0, "uuid": "e7915a74-618b-4908-9189-dce965465702"}'
return status_code, json.loads(resp)
def fake_node_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 2}, ' \
'"objects": [{"deployed_datetime": "Mon, 29 Sep 2014 22:29:03 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "fa9df19a-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:27:05 +0000", "node_cluster": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "198.199.97.190", "region": "/api/v1/region/digitalocean/sfo1/", "resource_uri": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "state": "Deployed", "uuid": "fa9df19a-162b-45b4-bb5a-152dfd1b133f"}, ' \
'{"deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:27:06 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Deployed", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}]}'
return status_code, json.loads(resp)
def fake_node_fetch():
status_code = 200
resp = '{"actions": ["/api/v1/action/8f5b893b-826e-40b7-bb8b-2d96301425f2/"], "deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:30:06 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Deployed", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}'
return status_code, json.loads(resp)
def fake_node_delete():
status_code = 202
resp = '{"actions": ["/api/v1/action/8f5b893b-826e-40b7-bb8b-2d96301425f2/", "/api/v1/action/83c66611-ea5d-45d7-a97d-914029a90524/"], "deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:38:12 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Terminating", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}'
return status_code, json.loads(resp)
def fake_node_deploy():
status_code = 202
resp = '{"actions": ["/api/v1/action/8f5b893b-826e-40b7-bb8b-2d96301425f2/", "/api/v1/action/83c66611-ea5d-45d7-a97d-914029a90524/"], "deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:38:12 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Starting", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}'
return status_code, json.loads(resp)
def fake_image_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 1}, "objects": [{"base_image": false, "categories": [], "cluster_aware": true, "description": "", "docker_registry": "/api/v1/registry/docker.com/", "image_url": "", "imagetag_set": ["/api/v1/image/docker.com/tifayuki/mongodb/tag/latest/"], "is_private_image": true, "name": "docker.com/tifayuki/mongodb", "public_url": "", "resource_uri": "/api/v1/image/docker.com/tifayuki/mongodb/", "starred": false}]}'
return status_code, json.loads(resp)
def fake_image_fetch():
status_code = 200
resp = '{"base_image": false, "categories": [], "cluster_aware": true, "description": "", "docker_registry": {"created": true, "host": "registry.hub.docker.com", "id": 5, "image_url": "/_static/assets/images/dockerregistries/docker.png", "is_ssl": true, "is_tutum_registry": false, "modified": true, "name": "Docker.io", "resource_uri": "/api/v1/registry/registry.hub.docker.com/", "uuid": "d533039e-c44c-4cdc-951b-e0e03b8410c6"}, "image_url": "", "imagetag_set": [{"full_name": "tifayuki/cadvisor:latest", "image": {"author": "fake <fake@docker.com>", "docker_id": "9e2907ef52bf811b4da100f50ba8f0908ccc610c7054bd69087f0a9f4703efdd", "entrypoint": "", "image_creation": "Fri, 15 Aug 2014 15:19:04 +0000", "imageenvvar_set": [{"key": "CADVISOR_TAG", "value": "0.2.2"}, {"key": "DB_NAME", "value": "cadvisor"}, {"key": "DB_PASS", "value": "root"}, {"key": "DB_USER", "value": "root"}, {"key": "HOME", "value": "/"}, {"key": "PATH", "value": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}], "imageport_set": [], "run_command": "/run.sh"}, "image_info": "/api/v1/image/tifayuki/cadvisor/", "name": "latest", "resource_uri": "/api/v1/image/tifayuki/cadvisor/tag/latest/"}], "is_private_image": true, "name": "tifayuki/cadvisor", "public_url": "https://registry.hub.docker.com/u/tifayuki/cadvisor/", "resource_uri": "/api/v1/image/tifayuki/cadvisor/", "starred": false}'
return status_code, json.loads(resp)
def fake_image_save():
status_code = 202
resp = '{"base_image": false, "categories": [], "cluster_aware": true, "description": "description", "docker_registry": {"created": true, "host": "registry.hub.docker.com", "id": 5, "image_url": "/_static/assets/images/dockerregistries/docker.png", "is_ssl": true, "is_tutum_registry": false, "modified": true, "name": "Docker.io", "resource_uri": "/api/v1/registry/registry.hub.docker.com/", "uuid": "d533039e-c44c-4cdc-951b-e0e03b8410c6"}, "image_url": "", "imagetag_set": [{"full_name": "tifayuki/cadvisor:latest", "image": {"author": "fake <fake@docker.com>", "docker_id": "9e2907ef52bf811b4da100f50ba8f0908ccc610c7054bd69087f0a9f4703efdd", "entrypoint": "", "image_creation": "Fri, 15 Aug 2014 15:19:04 +0000", "imageenvvar_set": [{"key": "CADVISOR_TAG", "value": "0.2.2"}, {"key": "DB_NAME", "value": "cadvisor"}, {"key": "DB_PASS", "value": "root"}, {"key": "DB_USER", "value": "root"}, {"key": "HOME", "value": "/"}, {"key": "PATH", "value": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}], "imageport_set": [], "run_command": "/run.sh"}, "image_info": "/api/v1/image/tifayuki/cadvisor/", "name": "latest", "resource_uri": "/api/v1/image/tifayuki/cadvisor/tag/latest/"}], "is_private_image": true, "name": "tifayuki/cadvisor", "public_url": "https://registry.hub.docker.com/u/tifayuki/cadvisor/", "resource_uri": "/api/v1/image/tifayuki/cadvisor/", "starred": false}'
return status_code, json.loads(resp)
def fake_image_delete():
status_code = 204
resp = ''
return status_code, resp
def fake_service_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 1}, ' \
'"objects": [{"autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "cpu_shares": null, "current_num_containers": 3, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "state": "Running", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 3, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8"}]}'
return status_code, json.loads(resp)
def fake_service_fetch():
status_code = 200
resp = '{"actions": ["/api/v1/action/e3ee01df-9f2f-4720-a114-ea1a236d47d2/", "/api/v1/action/c58213ab-8d5c-4a6d-b4c3-bd7157242dc2/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "containers": ["/api/v1/container/cff4dfa7-28a5-4599-a3f9-c7dc39353c11/", "/api/v1/container/4d966087-5169-4a0b-a2f0-78bbb878d872/", "/api/v1/container/0c84cd78-c239-40ad-939e-dbbc372ae345/"], "cpu_shares": null, "current_num_containers": 3, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "link_variables": {"HELLO_WORLD_1_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_1_ENV_HOME": "/", "HELLO_WORLD_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_1_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_1_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_1_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_2_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_2_ENV_HOME": "/", "HELLO_WORLD_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_2_PORT": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP_ADDR": "hello-world-2.a2f5a2e9-tifayuki.node.docker.io", "HELLO_WORLD_2_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_2_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_3_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_3_ENV_HOME": "/", "HELLO_WORLD_3_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_3_PORT": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP_ADDR": "hello-world-3.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_3_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_3_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_ENV_HOME": "/", "HELLO_WORLD_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "roles": [], "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "state": "Running", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 3, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8"}'
return status_code, json.loads(resp)
def fake_service_save():
status_code = 202
resp = '{"actions": ["/api/v1/action/e3ee01df-9f2f-4720-a114-ea1a236d47d2/", "/api/v1/action/c58213ab-8d5c-4a6d-b4c3-bd7157242dc2/", "/api/v1/action/b3adfa79-dbd2-41a4-8c71-5e242ecce9bb/", "/api/v1/action/f82e25e7-d550-454e-a897-8599f2f530e5/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "containers": ["/api/v1/container/cff4dfa7-28a5-4599-a3f9-c7dc39353c11/", "/api/v1/container/4d966087-5169-4a0b-a2f0-78bbb878d872/", "/api/v1/container/0c84cd78-c239-40ad-939e-dbbc372ae345/"], "cpu_shares": null, "current_num_containers": 3, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "link_variables": {"HELLO_WORLD_1_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_1_ENV_HOME": "/", "HELLO_WORLD_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_1_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_1_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_1_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_2_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_2_ENV_HOME": "/", "HELLO_WORLD_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_2_PORT": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP_ADDR": "hello-world-2.a2f5a2e9-tifayuki.node.docker.io", "HELLO_WORLD_2_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_2_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_3_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_3_ENV_HOME": "/", "HELLO_WORLD_3_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_3_PORT": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP_ADDR": "hello-world-3.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_3_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_3_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_ENV_HOME": "/", "HELLO_WORLD_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "roles": [], "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "state": "Scaling", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 5, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8", "web_public_dns": ""}'
return status_code, json.loads(resp)
def fake_service_delete():
status_code = 202
resp = '{"actions": ["/api/v1/action/e3ee01df-9f2f-4720-a114-ea1a236d47d2/", "/api/v1/action/c58213ab-8d5c-4a6d-b4c3-bd7157242dc2/", "/api/v1/action/b3adfa79-dbd2-41a4-8c71-5e242ecce9bb/", "/api/v1/action/f82e25e7-d550-454e-a897-8599f2f530e5/", "/api/v1/action/381c53a0-bf18-4a01-a1b0-be87051c35e8/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "containers": ["/api/v1/container/cff4dfa7-28a5-4599-a3f9-c7dc39353c11/", "/api/v1/container/4d966087-5169-4a0b-a2f0-78bbb878d872/", "/api/v1/container/0c84cd78-c239-40ad-939e-dbbc372ae345/", "/api/v1/container/6e74df59-83ee-4351-8ba9-3d26e0d64c34/", "/api/v1/container/7bbff9f0-af41-408f-9dd0-213cd67e4aa2/"], "cpu_shares": null, "current_num_containers": 5, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "link_variables": {"HELLO_WORLD_1_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_1_ENV_HOME": "/", "HELLO_WORLD_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_1_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_1_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_1_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_2_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_2_ENV_HOME": "/", "HELLO_WORLD_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_2_PORT": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP_ADDR": "hello-world-2.a2f5a2e9-tifayuki.node.docker.io", "HELLO_WORLD_2_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_2_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_3_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_3_ENV_HOME": "/", "HELLO_WORLD_3_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_3_PORT": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP_ADDR": "hello-world-3.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_3_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_3_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_4_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_4_ENV_HOME": "/", "HELLO_WORLD_4_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_4_PORT": "tcp://hello-world-4.5067d4f4-tifayuki.node.docker.io:49156", "HELLO_WORLD_4_PORT_80_TCP": "tcp://hello-world-4.5067d4f4-tifayuki.node.docker.io:49156", "HELLO_WORLD_4_PORT_80_TCP_ADDR": "hello-world-4.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_4_PORT_80_TCP_PORT": "49156", "HELLO_WORLD_4_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_5_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_5_ENV_HOME": "/", "HELLO_WORLD_5_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_5_PORT": "tcp://hello-world-5.fa9df19a-tifayuki.node.docker.io:49157", "HELLO_WORLD_5_PORT_80_TCP": "tcp://hello-world-5.fa9df19a-tifayuki.node.docker.io:49157", "HELLO_WORLD_5_PORT_80_TCP_ADDR": "hello-world-5.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_5_PORT_80_TCP_PORT": "49157", "HELLO_WORLD_5_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_ENV_HOME": "/", "HELLO_WORLD_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-t* Connection #0 to host dashboard.docker.com left intactifayuki.node.docker.io", "HELLO_WORLD_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "roles": [], "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 22:57:36 +0000", "state": "Terminating", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 0, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8"}'
return status_code, json.loads(resp)
def fake_service_start():
status_code = 202
resp = '{"actions": ["/api/v1/action/a8aaf64b-3186-41e7-9256-6b3d69786036/", "/api/v1/action/2481790d-a860-4bb4-95d2-5676ae2d6748/", "/api/v1/action/799a0d06-efae-4bf1-b063-7141f722cbb1/", "/api/v1/action/ef6b9f59-1edb-44c7-bcc6-1b80f737e4b0/", "/api/v1/action/99b7ac29-d16d-448f-bac5-54dbd5dd3b7b/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 3306, "outer_port": null, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "containers": ["/api/v1/container/2a1c4057-7753-4393-98c6-35699c198e08/", "/api/v1/container/54ead360-698f-4354-96f7-538f686cdd69/"], "cpu_shares": null, "current_num_containers": 2, "deployed_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "link_variables": {"MYSQL_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "mysql", "resource_uri": "/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/", "roles": [], "run_command": "/run.sh", "running_num_containers": 0, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "state": "Starting", "stopped_datetime": "Tue, 30 Sep 2014 23:09:09 +0000", "stopped_num_containers": 2, "target_num_containers": 2, "unique_name": "mysql", "uuid": "5ecde92d-498b-4bbb-b773-a998e5e421dc"}'
return status_code, json.loads(resp)
def fake_service_stop():
status_code = 202
resp = '{"actions": ["/api/v1/action/a8aaf64b-3186-41e7-9256-6b3d69786036/", "/api/v1/action/2481790d-a860-4bb4-95d2-5676ae2d6748/", "/api/v1/action/799a0d06-efae-4bf1-b063-7141f722cbb1/", "/api/v1/action/ef6b9f59-1edb-44c7-bcc6-1b80f737e4b0/", "/api/v1/action/99b7ac29-d16d-448f-bac5-54dbd5dd3b7b/", "/api/v1/action/98816bfc-4fa7-4697-bea1-d926de69b48f/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 3306, "outer_port": null, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "containers": ["/api/v1/container/2a1c4057-7753-4393-98c6-35699c198e08/", "/api/v1/container/54ead360-698f-4354-96f7-538f686cdd69/"], "cpu_shares": null, "current_num_containers": 2, "deployed_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "link_variables": {"MYSQL_1_ENV_DEBIAN_FRONTEND": "noninteractive", "MYSQL_1_ENV_MYSQL_PASS": "**Random**", "MYSQL_1_ENV_MYSQL_USER": "admin", "MYSQL_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "MYSQL_1_ENV_REPLICATION_MASTER": "**False**", "MYSQL_1_ENV_REPLICATION_PASS": "replica", "MYSQL_1_ENV_REPLICATION_SLAVE": "**False**", "MYSQL_1_ENV_REPLICATION_USER": "replica", "MYSQL_1_PORT": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_1_PORT_3306_TCP": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_1_PORT_3306_TCP_ADDR": "mysql-1.fa9df19a-tifayuki.node.docker.io", "MYSQL_1_PORT_3306_TCP_PORT": "49156", "MYSQL_1_PORT_3306_TCP_PROTO": "tcp", "MYSQL_2_ENV_DEBIAN_FRONTEND": "noninteractive", "MYSQL_2_ENV_MYSQL_PASS": "**Random**", "MYSQL_2_ENV_MYSQL_USER": "admin", "MYSQL_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "MYSQL_2_ENV_REPLICATION_MASTER": "**False**", "MYSQL_2_ENV_REPLICATION_PASS": "replica", "MYSQL_2_ENV_REPLICATION_SLAVE": "**False**", "MYSQL_2_ENV_REPLICATION_USER": "replica", "MYSQL_2_PORT": "tcp://mysql-2.a2f5a2e9-tifayuki.node.docker.io:49156", "MYSQL_2_PORT_3306_TCP": "tcp://mysql-2.a2f5a2e9-tifayuki.node.docker.io:49156", "MYSQL_2_PORT_3306_TCP_ADDR": "mysql-2.a2f5a2e9-tifayuki.node.docker.io", "MYSQL_2_PORT_3306_TCP_PORT": "49156", "MYSQL_2_PORT_3306_TCP_PROTO": "tcp", "MYSQL_ENV_DEBIAN_FRONTEND": "noninteractive", "MYSQL_ENV_MYSQL_PASS": "**Random**", "MYSQL_ENV_MYSQL_USER": "admin", "MYSQL_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "MYSQL_ENV_REPLICATION_MASTER": "**False**", "MYSQL_ENV_REPLICATION_PASS": "replica", "MYSQL_ENV_REPLICATION_SLAVE": "**False**", "MYSQL_ENV_REPLICATION_USER": "replica", "MYSQL_PORT": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_PORT_3306_TCP": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_PORT_3306_TCP_ADDR": "mysql-1.fa9df19a-tifayuki.node.docker.io", "MYSQL_PORT_3306_TCP_PORT": "49156", "MYSQL_PORT_3306_TCP_PROTO": "tcp", "MYSQL_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "mysql", "resource_uri": "/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/", "roles": [], "run_command": "/run.sh", "running_num_containers": 1, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 23:50:59 +0000", "state": "Stopping", "stopped_datetime": "Tue, 30 Sep 2014 23:09:09 +0000", "stopped_num_containers": 0, "target_num_containers": 2, "unique_name": "mysql", "uuid": "5ecde92d-498b-4bbb-b773-a998e5e421dc"}'
return status_code, json.loads(resp)
def fake_service_redeploy():
status_code = 202
resp = '{"actions": ["/api/v1/action/a8aaf64b-3186-41e7-9256-6b3d69786036/", "/api/v1/action/2481790d-a860-4bb4-95d2-5676ae2d6748/", "/api/v1/action/799a0d06-efae-4bf1-b063-7141f722cbb1/", "/api/v1/action/ef6b9f59-1edb-44c7-bcc6-1b80f737e4b0/", "/api/v1/action/99b7ac29-d16d-448f-bac5-54dbd5dd3b7b/", "/api/v1/action/98816bfc-4fa7-4697-bea1-d926de69b48f/", "/api/v1/action/285e250f-7429-4f0a-a252-5594a05c8020/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 3306, "outer_port": null, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "containers": ["/api/v1/container/2a1c4057-7753-4393-98c6-35699c198e08/", "/api/v1/container/54ead360-698f-4354-96f7-538f686cdd69/"], "cpu_shares": null, "current_num_containers": 2, "deployed_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "link_variables": {"MYSQL_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "mysql", "resource_uri": "/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/", "roles": [], "run_command": "/run.sh", "running_num_containers": 0, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 23:50:59 +0000", "state": "Redeploying", "stopped_datetime": "Tue, 30 Sep 2014 23:51:52 +0000", "stopped_num_containers": 0, "target_num_containers": 2, "unique_name": "mysql", "uuid": "5ecde92d-498b-4bbb-b773-a998e5e421dc"}'
return status_code, json.loads(resp)
def fake_service_logs():
status_code = 200
resp = r'{"logs": "[mysql-1] 2014-09-30T22:44:31.385643366Z => An empty or uninitialized MySQL volume is detected in /var/lib/mysql\n[mysql-1] 2014-09-30T22:44:31.386200239Z => Installing MySQL ...\n"}'
return status_code, json.loads(resp)
def fake_container_list():
status_code = 200
resp = '{"meta": {"limit": 25, "next": null, "offset": 0, "previous": null, "total_count": 2}, ' \
'"objects": [{"autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_ports": [{"endpoint_uri": "mysql://mysql-1.fa9df19a-tifayuki.node.docker.io:49159/", "inner_port": 3306, "outer_port": 49159, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:44:32 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "is_dead_backend": null, "memory": null, "memory_swap": null, "name": "mysql", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "mysql-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/567f1ff8-57bd-4689-a732-1e1705bc5082/", "run_command": "/run.sh", "service": "/api/v1/service/326a2daf-2069-4cd4-9e44-08faa068a62f/", "started_datetime": "Wed, 1 Oct 2014 14:44:32 +0000", "state": "Running", "stopped_datetime": null, "unique_name": "mysql-1", "uuid": "567f1ff8-57bd-4689-a732-1e1705bc5082"}, {"autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Running", "stopped_datetime": null, "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}]}'
return status_code, json.loads(resp)
def fake_container_fetch():
status_code = 200
resp = '{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Running", "stopped_datetime": null, "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
return status_code, json.loads(resp)
def fake_container_delete():
status_code = 202
resp = '{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/", "/api/v1/action/5c435933-a1d9-449e-acb6-fbe7b5904b26/", "/api/v1/action/2508d37c-57a8-4ef1-88ff-3b5e20f88b7f/", "/api/v1/action/0438248f-b05c-41f2-b715-40a788735deb/", "/api/v1/action/6a5b82a0-b14a-4c05-94d0-117d730cb647/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": 0, "exit_code_msg": "Exit code 0 (Success)", "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 15:22:51 +0000", "state": "Terminating", "stopped_datetime": "Wed, 1 Oct 2014 15:20:58 +0000", "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
return status_code, json.loads(resp)
def fake_container_start():
status_code = 202
resp = '{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/", "/api/v1/action/5c435933-a1d9-449e-acb6-fbe7b5904b26/", "/api/v1/action/2508d37c-57a8-4ef1-88ff-3b5e20f88b7f/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": 0, "exit_code_msg": "Exit code 0 (Success)", "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Starting", "stopped_datetime": "Wed, 1 Oct 2014 15:20:58 +0000", "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
return status_code, json.loads(resp)
def fake_container_stop():
status_code = 202
resp = '{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Stopping", "stopped_datetime": null, "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
return status_code, json.loads(resp)
def fake_container_logs():
status_code = 200
resp = r'{"logs": "2014-10-01T14:54:23.173185119Z => An empty or uninitialized MySQL volume is detected in /var/lib/mysql\n2014-10-01T14:54:23.173350403Z => Installing MySQL ...\n2014-10-01T14:54:23.467005403Z => Done!\n"}'
return status_code, json.loads(resp)

35
tests/test_action.py Normal file
View file

@ -0,0 +1,35 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class ActionTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_action_list(self, mock_send):
attributes = json.loads(
'[{"action": "Node Cluster Create", "end_date": "Mon, 29 Sep 2014 15:40:59 +0000", "ip": "207.41.188.212", "location": "New York, United States", "method": "POST", "object": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "path": "/api/v1/nodecluster/", "resource_uri": "/api/v1/action/7f62b667-2693-420a-ad2e-41cda5605322/", "start_date": "Mon, 29 Sep 2014 15:40:59 +0000", "state": "Success", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "7f62b667-2693-420a-ad2e-41cda5605322"}, ' \
'{"action": "Node Cluster Deploy", "end_date": "Mon, 29 Sep 2014 15:41:01 +0000", "ip": "207.41.188.212", "location": "New York, United States", "method": "POST", "object": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "path": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/deploy/", "resource_uri": "/api/v1/action/db69b048-3bab-4a2e-bcbd-91265edf1a31/", "start_date": "Mon, 29 Sep 2014 15:41:00 +0000", "state": "Failed", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "db69b048-3bab-4a2e-bcbd-91265edf1a31"}, ' \
'{"action": "Node Deploy", "end_date": "Mon, 29 Sep 2014 15:41:16 +0000", "ip": "207.41.188.212", "location": "New York, United States", "method": "POST", "object": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "path": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/deploy/", "resource_uri": "/api/v1/action/ce9ae16b-88fa-4be6-b12e-fc970b8d2445/", "start_date": "Mon, 29 Sep 2014 15:41:16 +0000", "state": "Failed", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "ce9ae16b-88fa-4be6-b12e-fc970b8d2445"}]'
)
mock_send.return_value = fake_resp(fake_action_list)
actions = dockercloud.Action.list()
for i in range(0, len(actions)):
result = json.loads(json.dumps(actions[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_action_fetch(self, mock_send):
attribute = json.loads(
'{"action": "Node Cluster Create", "end_date": "Mon, 29 Sep 2014 15:40:59 +0000", "ip": "207.41.188.212", "location": "New York, United States", "logs": "", "method":"POST", "object": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "path": "/api/v1/nodecluster/", "resource_uri": "/api/v1/action/7f62b667-2693-420a-ad2e-41cda5605322/", "start_date": "Mon, 29 Sep 2014 15:40:59 +0000", "state": "Success", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36", "uuid": "7f62b667-2693-420a-ad2e-41cda5605322"}'
)
mock_send.return_value = fake_resp(fake_action_fetch)
action = dockercloud.Action.fetch("7f62b667-2693-420a-ad2e-41cda5605322")
result = json.loads(json.dumps(action.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

89
tests/test_auth.py Normal file
View file

@ -0,0 +1,89 @@
from __future__ import absolute_import
import os
import tempfile
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class AuthTestCase(unittest.TestCase):
def setUp(self):
self.dockercloud_auth = dockercloud.dockercloud_auth
self.basic_auth = dockercloud.basic_auth
def tearDown(self):
dockercloud.dockercloud_auth = self.dockercloud_auth
dockercloud.basic_auth = self.basic_auth
@mock.patch('dockercloud.api.auth.verify_credential')
def test_auth_authenticate(self, mock_verify_credential):
dockercloud.auth.authenticate(FAKE_USER, FAKE_PASSWORD)
mock_verify_credential.assert_called_with(FAKE_USER, FAKE_PASSWORD)
self.assertEqual(dockercloud.basic_auth, FAKE_BASIC_AUTH)
self.tearDown()
def test_auth_is_authenticated(self):
dockercloud.dockercloud_auth = FAKE_DOCKERCLOUD_AUTH
dockercloud.basic_auth = FAKE_BASIC_AUTH
self.assertTrue(dockercloud.auth.is_authenticated())
dockercloud.dockercloud_auth = None
dockercloud.basic_auth = FAKE_BASIC_AUTH
self.assertTrue(dockercloud.auth.is_authenticated())
dockercloud.dockercloud_auth = FAKE_DOCKERCLOUD_AUTH
dockercloud.basic_auth = None
dockercloud.apikey_auth = None
self.assertTrue(dockercloud.auth.is_authenticated())
dockercloud.dockercloud_auth = None
dockercloud.basic_auth = None
self.assertFalse(dockercloud.auth.is_authenticated())
def test_auth_logout(self):
dockercloud.dockercloud_auth = FAKE_DOCKERCLOUD_AUTH
dockercloud.basic_auth = FAKE_BASIC_AUTH
dockercloud.auth.logout()
self.assertIsNone(dockercloud.dockercloud_auth)
self.assertIsNone(dockercloud.basic_auth)
def test_auth_load_from_file(self):
temp = tempfile.NamedTemporaryFile('w', delete=False)
with temp as f:
f.write('''{
"auths": {
"https://index.docker.io/v1/": {
"auth": "%s",
"email": "tifayuki@gmail.com"
}
}
}''' % FAKE_BASIC_AUTH)
basic_auth = dockercloud.auth.load_from_file(f.name)
self.assertEqual(basic_auth, FAKE_BASIC_AUTH)
os.remove(temp.name)
def test_auth_load_from_file_with_exception(self):
basic_auth = dockercloud.auth.load_from_file('abc')
self.assertIsNone(basic_auth)
def test_auth_get_auth_header(self):
dockercloud.dockercloud_auth = FAKE_DOCKERCLOUD_AUTH
dockercloud.basic_auth = FAKE_BASIC_AUTH
self.assertEqual({'Authorization': FAKE_DOCKERCLOUD_AUTH}, dockercloud.auth.get_auth_header())
print "===================="
dockercloud.dockercloud_auth = None
dockercloud.basic_auth = FAKE_BASIC_AUTH
self.assertEqual({'Authorization': 'Basic %s' % (FAKE_BASIC_AUTH)}, dockercloud.auth.get_auth_header())
dockercloud.dockercloud_auth = FAKE_DOCKERCLOUD_AUTH
dockercloud.basic_auth = None
self.assertEqual({'Authorization': FAKE_DOCKERCLOUD_AUTH}, dockercloud.auth.get_auth_header())
dockercloud.dockercloud_auth = None
dockercloud.basic_auth = None
self.assertEqual({}, dockercloud.auth.get_auth_header())

252
tests/test_base.py Normal file
View file

@ -0,0 +1,252 @@
from __future__ import absolute_import
import json
import unittest
import unittest.mock as mock
import dockercloud
from dockercloud.api.base import Restful, Mutable, Immutable
class RestfulTestCase(unittest.TestCase):
def setUp(self):
self.pk = Restful.pk
Restful.pk = 'uuid'
def tearDown(self):
Restful.pk = self.pk
def test_restful_init(self):
model = Restful(key1='value1', key2='value2')
self.assertEqual('value1', model.key1)
self.assertEqual('value2', model.key2)
def test_restful_setattr(self):
model = Restful()
setattr(model, 'key', 'value')
self.assertEqual('value', model.key)
self.assertEqual(['key'], model.__changedattrs__)
setattr(model, 'key', 'other value')
self.assertEqual('other value', model.key)
self.assertEqual(['key'], model.__changedattrs__)
setattr(model, 'another_key', 'another_value')
self.assertEqual('another_value', model.another_key)
self.assertEqual(['key', 'another_key'], model.__changedattrs__)
def test_restful_getchanges(self):
model = Restful()
self.assertEqual([], model.__getchanges__())
model.__changedattrs__ = ['dockercloud']
self.assertEqual(['dockercloud'], model.__getchanges__())
def test_restful_setchanges(self):
model = Restful()
model.__setchanges__('abc')
self.assertEqual('abc', model.__changedattrs__)
model.__setchanges__(None)
self.assertIsNone(model.__changedattrs__)
def test_restful_loaddict(self):
model = Restful()
self.assertRaises(AssertionError, model._loaddict, {'key': 'value'})
model.endpoint = 'endpoint'
model.subsystem = "subsystem"
model._loaddict({'key': 'value'})
self.assertEqual('value', model.key)
self.assertEqual("/".join(["api", model.subsystem, model._api_version, model.endpoint.lstrip("/"), model.pk]),
model._detail_uri)
self.assertEqual([], model.__getchanges__())
def test_restful_pk(self):
model = Restful()
self.assertEqual(model.__class__._pk_key(), model.pk)
def test_restful_is_dirty(self):
model = Restful()
self.assertFalse(model.is_dirty)
model.key = 'value'
self.assertTrue(model.is_dirty)
@mock.patch('dockercloud.api.base.send_request')
def test_restful_perform_action(self, mock_send_request):
try:
model = Restful()
self.assertRaises(dockercloud.ApiError, model._perform_action, 'action')
model.endpoint = 'fake'
model.subsystem = "subsystem"
model._detail_uri = "/".join(
["api", model.subsystem, model._api_version, model.endpoint.lstrip("/"), model.pk])
mock_send_request.side_effect = [{'key': 'value'}, None]
self.assertTrue(model._perform_action('action', params={'k': 'v'}, data={'key': 'value'}))
self.assertEqual('value', model.key)
mock_send_request.assert_called_with('POST', "/".join([model._detail_uri, "action"]), data={'key': 'value'},
params={'k': 'v'})
self.assertFalse(model._perform_action('action', {'key': 'value'}))
finally:
if hasattr(Restful, 'endpoint'):
delattr(Restful, 'endpoint')
@mock.patch('dockercloud.api.base.send_request')
def test_restful_expand_attribute(self, mock_send_request):
model = Restful()
self.assertRaises(dockercloud.ApiError, model._expand_attribute, 'attribute')
model._detail_uri = 'fake/uuid'
mock_send_request.side_effect = [{'key': 'value'}, None]
self.assertEqual('value', model._expand_attribute('key'))
self.assertIsNone(model._expand_attribute('key'))
def test_restful_get_all_attributes(self):
model = Restful()
model.key = 'value'
self.assertDictEqual({'key': 'value'}, model.get_all_attributes())
class ImmutableTestCase(unittest.TestCase):
def setUp(self):
self.pk = Immutable.pk
Immutable.pk = 'uuid'
def tearDown(self):
Immutable.pk = self.pk
@mock.patch('dockercloud.api.base.send_request')
def test_immutable_fetch(self, mock_send_request):
self.assertRaises(AssertionError, Immutable.fetch, 'uuid')
try:
ret_json = {"key": "value"}
mock_send_request.return_value = ret_json
Immutable.endpoint = 'endpoint'
Immutable.subsystem = "subsystem"
model = Immutable.fetch('uuid')
mock_send_request.assert_called_with('GET', 'api/subsystem/%s/endpoint/uuid' % Immutable._api_version)
self.assertIsInstance(model, Immutable)
self.assertEqual('value', model.key)
finally:
if hasattr(Immutable, 'endpoint'):
delattr(Immutable, 'endpoint')
@mock.patch('dockercloud.api.base.send_request')
def test_immutable_list(self, mock_send_request):
self.assertRaises(AssertionError, Immutable.list)
try:
kwargs = {'key': 'value'}
ret_json = {"meta": {"limit": 25, "next": None, "offset": 0, "previous": None, "total_count": 1},
"objects": [{"key": "value1"}, {"key": "value2"}]}
mock_send_request.return_value = ret_json
Immutable.endpoint = 'fake'
models = Immutable.list(**kwargs)
mock_send_request.assert_called_with('GET', 'api/subsystem/%s/fake' % Immutable._api_version, params=kwargs)
self.assertEqual(2, len(models))
self.assertIsInstance(models[0], Immutable)
self.assertEqual('value1', models[0].key)
self.assertIsInstance(models[1], Immutable)
self.assertEqual('value2', models[1].key)
finally:
if hasattr(Immutable, 'endpoint'):
delattr(Immutable, 'endpoint')
@mock.patch('dockercloud.api.base.send_request')
def test_immutable_refresh(self, mock_send_request):
try:
model = Immutable()
model.key = 'value'
self.assertFalse(model.refresh(force=False))
self.assertRaises(dockercloud.ApiError, model.refresh, force=True)
Immutable.endpoint = 'endpoint'
Immutable.subsystem = 'subsystem'
model._detail_uri = 'api/subsystem/%s/endpoint/uuid' % Immutable._api_version
mock_send_request.side_effect = [{'newkey': 'newvalue'}, None]
self.assertTrue(model.refresh(force=True))
self.assertEqual('newvalue', model.newkey)
mock_send_request.assert_called_with('GET', model._detail_uri)
self.assertFalse(model.refresh(force=True))
mock_send_request.assert_called_with('GET', model._detail_uri)
finally:
if hasattr(Immutable, 'endpoint'):
delattr(Immutable, 'endpoint')
class MutableTestCase(unittest.TestCase):
def setUp(self):
self.pk = Mutable.pk
Mutable.pk = 'uuid'
def tearDown(self):
Mutable.pk = self.pk
def test_mutable_create(self):
self.assertIsInstance(Mutable.create(), Mutable)
@mock.patch('dockercloud.api.base.send_request')
def test_mutable_delete(self, mock_send_request):
try:
model = Mutable()
self.assertRaises(dockercloud.ApiError, model.delete)
Mutable.endpoint = 'fake'
model._detail_uri = 'fake/uuid'
mock_send_request.side_effect = [{'key': 'value'}, None]
self.assertTrue(model.delete())
self.assertEqual('value', model.key)
mock_send_request.assert_called_with('DELETE', 'fake/uuid')
self.assertTrue(model.delete())
self.assertIsNone(model._detail_uri)
self.assertFalse(model.is_dirty)
finally:
if hasattr(Mutable, 'endpoint'):
delattr(Mutable, 'endpoint')
@mock.patch('dockercloud.api.base.send_request')
def test_mutable_save(self, mock_send_request):
try:
self.assertTrue(Mutable().save())
model = Mutable()
model.key = 'value'
self.assertRaises(AssertionError, model.save)
Mutable.endpoint = 'endpoint'
Mutable.subsystem = 'subsystem'
mock_send_request.return_value = None
result = model.save()
mock_send_request.assert_called_with('POST', 'api/subsystem/%s/endpoint' % Mutable._api_version,
data=json.dumps({'key': 'value'}))
self.assertFalse(result)
mock_send_request.return_value = {'newkey': 'newvalue'}
result = model.save()
mock_send_request.assert_called_with('POST', 'api/subsystem/%s/endpoint' % Mutable._api_version,
data=json.dumps({'key': 'value'}))
self.assertTrue(result)
self.assertEqual('newvalue', model.newkey)
model.key = 'another value'
mock_send_request.return_value = {'newkey2': 'newvalue2'}
model._detail_uri = 'api/subsystem/%s/endpoint/uuid' % Immutable._api_version
result = model.save()
mock_send_request.assert_called_with('PATCH', 'api/subsystem/%s/endpoint/uuid' % Mutable._api_version,
data=json.dumps({'key': 'another value'}))
self.assertTrue(result)
self.assertEqual('another value', model.key)
self.assertEqual('newvalue2', model.newkey2)
finally:
if hasattr(Mutable, 'endpoint'):
delattr(Mutable, 'endpoint')

70
tests/test_container.py Normal file
View file

@ -0,0 +1,70 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class ContainerTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_container_list(self, mock_send):
attributes = json.loads(
'[{"autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_ports": [{"endpoint_uri": "mysql://mysql-1.fa9df19a-tifayuki.node.docker.io:49159/", "inner_port": 3306, "outer_port": 49159, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:44:32 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "is_dead_backend": null, "memory": null, "memory_swap": null, "name": "mysql", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "mysql-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/567f1ff8-57bd-4689-a732-1e1705bc5082/", "run_command": "/run.sh", "service": "/api/v1/service/326a2daf-2069-4cd4-9e44-08faa068a62f/", "started_datetime": "Wed, 1 Oct 2014 14:44:32 +0000", "state": "Running", "stopped_datetime": null, "unique_name": "mysql-1", "uuid": "567f1ff8-57bd-4689-a732-1e1705bc5082"}, {"autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Running", "stopped_datetime": null, "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}]'
)
mock_send.return_value = fake_resp(fake_container_list)
containers = dockercloud.Container.list()
for i in range(0, len(containers)):
result = json.loads(json.dumps(containers[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_container_fetch(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Running", "stopped_datetime": null, "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
)
mock_send.return_value = fake_resp(fake_container_fetch)
container = dockercloud.Container.fetch('52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d')
self.assertTrue(container.start())
result = json.loads(json.dumps(container.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_container_delete(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/", "/api/v1/action/5c435933-a1d9-449e-acb6-fbe7b5904b26/", "/api/v1/action/2508d37c-57a8-4ef1-88ff-3b5e20f88b7f/", "/api/v1/action/0438248f-b05c-41f2-b715-40a788735deb/", "/api/v1/action/6a5b82a0-b14a-4c05-94d0-117d730cb647/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": 0, "exit_code_msg": "Exit code 0 (Success)", "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 15:22:51 +0000", "state": "Terminating", "stopped_datetime": "Wed, 1 Oct 2014 15:20:58 +0000", "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
)
mock_send.side_effect = [fake_resp(fake_container_fetch), fake_resp(fake_container_delete)]
container = dockercloud.Container.fetch('52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d')
self.assertTrue(container.delete())
result = json.loads(json.dumps(container.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_container_start(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/", "/api/v1/action/5c435933-a1d9-449e-acb6-fbe7b5904b26/", "/api/v1/action/2508d37c-57a8-4ef1-88ff-3b5e20f88b7f/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": 0, "exit_code_msg": "Exit code 0 (Success)", "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Starting", "stopped_datetime": "Wed, 1 Oct 2014 15:20:58 +0000", "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
)
mock_send.side_effect = [fake_resp(fake_container_fetch), fake_resp(fake_container_start)]
container = dockercloud.Container.fetch('52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d')
self.assertTrue(container.start())
result = json.loads(json.dumps(container.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_container_stop(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/62b1f681-5c0a-4b4e-8bc7-570f62020711/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": "http://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160/", "inner_port": 80, "outer_port": 49160, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}, {"endpoint_uri": "mysql://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161/", "inner_port": 3306, "outer_port": 49161, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "cpu_shares": null, "deployed_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "destroyed_datetime": null, "entrypoint": "", "exit_code": null, "exit_code_msg": null, "image_name": "tutum/wordpress:latest", "image_tag": "/api/v1/image/tutum/wordpress/tag/latest/", "is_dead_backend": null, "link_variables": {"WORDPRESS_1_ENV_HOME": "/", "WORDPRESS_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_1_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_1_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_1_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_1_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_3306_TCP_PORT": "49161", "WORDPRESS_1_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_1_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_1_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_1_PORT_80_TCP_PORT": "49160", "WORDPRESS_1_PORT_80_TCP_PROTO": "tcp", "WORDPRESS_ENV_HOME": "/", "WORDPRESS_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "WORDPRESS_ENV_PHP_POST_MAX_SIZE": "10M", "WORDPRESS_ENV_PHP_UPLOAD_MAX_FILESIZE": "10M", "WORDPRESS_PORT_3306_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49161", "WORDPRESS_PORT_3306_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_3306_TCP_PORT": "49161", "WORDPRESS_PORT_3306_TCP_PROTO": "tcp", "WORDPRESS_PORT_80_TCP": "tcp://wordpress-1.fa9df19a-tifayuki.node.docker.io:49160", "WORDPRESS_PORT_80_TCP_ADDR": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "WORDPRESS_PORT_80_TCP_PORT": "49160", "WORDPRESS_PORT_80_TCP_PROTO": "tcp"}, "memory": null, "memory_swap": null, "name": "wordpress", "node": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "public_dns": "wordpress-1.fa9df19a-tifayuki.node.docker.io", "resource_uri": "/api/v1/container/52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d/", "roles": [], "run_command": "/run.sh", "service": "/api/v1/service/81bbc30a-35de-4f5e-840d-87bc2573d818/", "started_datetime": "Wed, 1 Oct 2014 14:54:23 +0000", "state": "Stopping", "stopped_datetime": null, "unique_name": "wordpress-1", "uuid": "52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d"}'
)
mock_send.side_effect = [fake_resp(fake_container_fetch), fake_resp(fake_container_stop)]
container = dockercloud.Container.fetch('52fffbca-88b2-4eac-a66d-8f8ca7e3ff2d')
self.assertTrue(container.stop())
result = json.loads(json.dumps(container.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

33
tests/test_http.py Normal file
View file

@ -0,0 +1,33 @@
from __future__ import absolute_import
import unittest
import requests
import unittest.mock as mock
import dockercloud
from dockercloud.api.base import send_request
from .fake_api import fake_resp
class SendRequestTestCase(unittest.TestCase):
@mock.patch('dockercloud.api.http.Request', return_value=requests.Request('GET', 'http://fake.com'))
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_http_send_request(self, mock_send, mock_Request):
json_obj = {'key': 'value'}
mock_send.return_value = fake_resp(lambda: (None, json_obj))
self.assertRaises(dockercloud.ApiError, send_request, 'METHOD', 'path', data='data')
headers = {'Content-Type': 'application/json', 'User-Agent': 'python-dockercloud/%s' % dockercloud.__version__}
headers.update(dockercloud.auth.get_auth_header())
mock_send.return_value = fake_resp(lambda: (200, json_obj))
self.assertEqual(json_obj, send_request('METHOD', 'path'))
mock_send.return_value = fake_resp(lambda: (204, json_obj))
self.assertIsNone(send_request('METHOD', 'path'))
mock_send.return_value = fake_resp(lambda: (401, json_obj))
self.assertRaises(dockercloud.AuthError, send_request, 'METHOD', 'path')
mock_send.return_value = fake_resp(lambda: (500, json_obj))
self.assertRaises(dockercloud.ApiError, send_request, 'METHOD', 'path')

61
tests/test_node.py Normal file
View file

@ -0,0 +1,61 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class NodeTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_node_list(self, mock_send):
attributes = json.loads(
'[{"deployed_datetime": "Mon, 29 Sep 2014 22:29:03 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "fa9df19a-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:27:05 +0000", "node_cluster": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "198.199.97.190", "region": "/api/v1/region/digitalocean/sfo1/", "resource_uri": "/api/v1/node/fa9df19a-162b-45b4-bb5a-152dfd1b133f/", "state": "Deployed", "uuid": "fa9df19a-162b-45b4-bb5a-152dfd1b133f"}, ' \
'{"deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:27:06 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Deployed", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}]'
)
mock_send.return_value = fake_resp(fake_node_list)
nodes = dockercloud.Node.list()
for i in range(0, len(nodes)):
result = json.loads(json.dumps(nodes[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
def test_node_save(self):
self.assertRaises(AttributeError, dockercloud.Node().save)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_node_fetch(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/8f5b893b-826e-40b7-bb8b-2d96301425f2/"], "deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:30:06 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Deployed", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}'
)
mock_send.return_value = fake_resp(fake_node_fetch)
node = dockercloud.Node.fetch('43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456')
result = json.loads(json.dumps(node.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_node_delete(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/8f5b893b-826e-40b7-bb8b-2d96301425f2/", "/api/v1/action/83c66611-ea5d-45d7-a97d-914029a90524/"], "deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:38:12 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Terminating", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}'
)
mock_send.return_value = fake_resp(fake_node_delete)
node = dockercloud.Node.fetch('43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456')
self.assertTrue(node.delete())
result = json.loads(json.dumps(node.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_node_deploy(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/8f5b893b-826e-40b7-bb8b-2d96301425f2/", "/api/v1/action/83c66611-ea5d-45d7-a97d-914029a90524/"], "deployed_datetime": "Mon, 29 Sep 2014 22:59:47 +0000", "destroyed_datetime": null, "docker_execdriver": "native-0.2", "docker_graphdriver": "aufs", "docker_version": "1.2.0", "external_fqdn": "43b5ebaf-tifayuki.node.docker.io", "last_seen": "Tue, 30 Sep 2014 15:38:12 +0000", "node_cluster": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "public_ip": "178.62.20.100", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/", "state": "Starting", "uuid": "43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456"}'
)
mock_send.return_value = fake_resp(fake_node_deploy)
node = dockercloud.Node.fetch('43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456')
self.assertTrue(node.deploy())
result = json.loads(json.dumps(node.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

73
tests/test_nodecluster.py Normal file
View file

@ -0,0 +1,73 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class NodeClusterTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_nodecluster_list(self, mock_send):
attributes = json.loads(
'[{"current_num_nodes": 1, "deployed_datetime": "Mon, 29 Sep 2014 22:29:03 +0000", "destroyed_datetime": null, "name": "test", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "region": "/api/v1/region/digitalocean/sfo1/", "resource_uri": "/api/v1/nodecluster/a02c3763-e639-46fc-a6db-587f4dbb5444/", "state": "Deployed", "target_num_nodes": 1, "uuid": "a02c3763-e639-46fc-a6db-587f4dbb5444"}, '
'{"current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "test2", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "state": "Deploying", "target_num_nodes": 1, "uuid": "b616a720-6684-42c6-83bb-4d298b11b3f3"}]'
)
mock_send.return_value = fake_resp(fake_nodeclster_list)
clusters = dockercloud.NodeCluster.list()
for i in range(0, len(clusters)):
result = json.loads(json.dumps(clusters[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_nodecluster_fetch(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/bf02b00a-e2fc-4098-8b69-1424b659ef4a/", "/api/v1/action/f8dce6d4-5c41-46a9-9754-baa8f3cdf031/"], "current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "test2", "node_type": "/api/v1/nodetype/digitalocean/512mb/", "nodes": ["/api/v1/node/43b5ebaf-5b9c-4ed3-a1e5-3d91cea70456/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/b616a720-6684-42c6-83bb-4d298b11b3f3/", "state": "Init", "target_num_nodes": 1, "uuid": "b616a720-6684-42c6-83bb-4d298b11b3f3"}'
)
mock_send.return_value = fake_resp(fake_nodecluster_fetch)
cluster = dockercloud.NodeCluster.fetch('b616a720-6684-42c6-83bb-4d298b11b3f3')
result = json.loads(json.dumps(cluster.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_nodecluster_save(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/f47e26a6-c60c-416f-a0a9-ddf14e3aae83/"], "current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "my_cluster", "node_type": "/api/v1/nodetype/digitalocean/1gb/", "nodes": ["/api/v1/node/2cfe7823-f551-4c7b-a82c-f6ab31d7ca25/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/e7915a74-618b-4908-9189-dce965465702/", "state": "Init", "target_num_nodes": 1, "uuid": "e7915a74-618b-4908-9189-dce965465702"}'
)
mock_send.return_value = fake_resp(fake_nodecluster_save)
cluster = dockercloud.NodeCluster.create(name="my_cluster", region="/api/v1/region/digitalocean/lon1/",
node_type="/api/v1/nodetype/digitalocean/1gb/")
self.assertTrue(cluster.save())
result = json.loads(json.dumps(cluster.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_nodecluster_deploy(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/f47e26a6-c60c-416f-a0a9-ddf14e3aae83/", "/api/v1/action/d110016e-e65d-4ce7-9f11-50c6302494a6/"], "current_num_nodes": 1, "deployed_datetime": null, "destroyed_datetime": null, "name": "my_cluster", "node_type": "/api/v1/nodetype/digitalocean/1gb/", "nodes": ["/api/v1/node/2cfe7823-f551-4c7b-a82c-f6ab31d7ca25/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/e7915a74-618b-4908-9189-dce965465702/", "state": "Deploying", "target_num_nodes": 1, "uuid": "e7915a74-618b-4908-9189-dce965465702"}'
)
mock_send.side_effect = [fake_resp(fake_nodecluster_save), fake_resp(fake_nodecluster_deploy)]
cluster = dockercloud.NodeCluster.create(name="my_cluster", region="/api/v1/region/digitalocean/lon1/",
node_type="/api/v1/nodetype/digitalocean/1gb/")
cluster.save()
self.assertTrue(cluster.deploy())
result = json.loads(json.dumps(cluster.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_nodecluster_delete(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/f47e26a6-c60c-416f-a0a9-ddf14e3aae83/", "/api/v1/action/d110016e-e65d-4ce7-9f11-50c6302494a6/", "/api/v1/action/e33b4bb1-192b-46a6-a1ba-eadfc494c2dd/"], "current_num_nodes": 1, "deployed_datetime": "Mon, 29 Sep 2014 23:45:45 +0000", "destroyed_datetime": null, "name": "my_cluster", "node_type": "/api/v1/nodetype/digitalocean/1gb/", "nodes": ["/api/v1/node/2cfe7823-f551-4c7b-a82c-f6ab31d7ca25/"], "region": "/api/v1/region/digitalocean/lon1/", "resource_uri": "/api/v1/nodecluster/e7915a74-618b-4908-9189-dce965465702/", "state": "Terminating", "target_num_nodes": 0, "uuid": "e7915a74-618b-4908-9189-dce965465702"}'
)
mock_send.return_value = fake_resp(fake_nodecluster_delete)
cluster = dockercloud.NodeCluster.fetch('e7915a74-618b-4908-9189-dce965465702')
self.assertTrue(cluster.delete())
result = json.loads(json.dumps(cluster.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

View file

@ -0,0 +1,33 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class ProviderTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_provider_list(self, mock_send):
attributes = json.loads(
'[{"available": true, "label": "Digital Ocean", "name": "digitalocean", "regions": ["/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/ams3/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/sgp1/"], "resource_uri": "/api/v1/provider/digitalocean/"}]'
)
mock_send.return_value = fake_resp(fake_provider_list)
providers = dockercloud.Provider.list()
for i in range(0, len(providers)):
result = json.loads(json.dumps(providers[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_provider_fetch(self, mock_send):
attribute = json.loads(
'{"available": true, "label": "Digital Ocean", "name": "digitalocean", "regions": ["/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/ams3/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/sgp1/"], "resource_uri": "/api/v1/provider/digitalocean/"}'
)
mock_send.return_value = fake_resp(fake_provider_fetch)
provider = dockercloud.Provider.fetch("digitalocean")
result = json.loads(json.dumps(provider.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

41
tests/test_noderegion.py Normal file
View file

@ -0,0 +1,41 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class RegionTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_region_list(self, mock_send):
attrributes = json.loads(
'[{"availability_zones": [], "available": true, "label": "Amsterdam 1", "name": "ams1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/"], "resource_uri": "/api/v1/region/digitalocean/ams1/"}, ' \
'{"availability_zones": [], "available": true, "label": "San Francisco 1", "name": "sfo1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/sfo1/"}, ' \
'{"availability_zones": [], "available": true, "label": "New York 2", "name": "nyc2", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/nyc2/"}, ' \
'{"availability_zones": [], "available": true, "label": "Amsterdam 2", "name": "ams2", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/ams2/"}, ' \
'{"availability_zones": [], "available": true, "label": "Singapore 1", "name": "sgp1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/sgp1/"}, ' \
'{"availability_zones": [], "available": true, "label": "London 1", "name": "lon1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/lon1/"}, ' \
'{"availability_zones": [], "available": true, "label": "New York 3", "name": "nyc3", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/nyc3/"}, ' \
'{"availability_zones": [], "available": true, "label": "Amsterdam 3", "name": "ams3", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/", "/api/v1/nodetype/digitalocean/32gb/", "/api/v1/nodetype/digitalocean/48gb/", "/api/v1/nodetype/digitalocean/64gb/"], "resource_uri": "/api/v1/region/digitalocean/ams3/"}]'
)
mock_send.return_value = fake_resp(fake_region_list)
regions = dockercloud.Region.list()
for i in range(0, len(regions)):
result = json.loads(json.dumps(regions[i].get_all_attributes()))
target = json.loads(json.dumps(attrributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_region_fet(self, mock_send):
attribute = json.loads(
'{"availability_zones": [], "available": true, "label": "Amsterdam 1", "name": "ams1", "node_types": ["/api/v1/nodetype/digitalocean/512mb/", "/api/v1/nodetype/digitalocean/1gb/", "/api/v1/nodetype/digitalocean/2gb/", "/api/v1/nodetype/digitalocean/4gb/", "/api/v1/nodetype/digitalocean/8gb/", "/api/v1/nodetype/digitalocean/16gb/"], "provider": "/api/v1/provider/digitalocean/", "resource_uri": "/api/v1/region/digitalocean/ams1/"}'
)
mock_send.return_value = fake_resp(fake_region_fetch)
region = dockercloud.Region.fetch('digitalocean/asm1')
result = json.loads(json.dumps(region.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

41
tests/test_nodetype.py Normal file
View file

@ -0,0 +1,41 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class NodeTypeTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_nodetype_list(self, mock_send):
attributes = json.loads(
'[{"availability_zones": [], "available": true, "label": "512MB", "name": "512mb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/512mb/"}, ' \
'{"availability_zones": [], "available": true, "label": "1GB", "name": "1gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/1gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "2GB", "name": "2gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/2gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "4GB", "name": "4gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/4gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "8GB", "name": "8gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/8gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "16GB", "name": "16gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/16gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "32GB", "name": "32gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/32gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "48GB", "name": "48gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/48gb/"}, ' \
'{"availability_zones": [], "available": true, "label": "64GB", "name": "64gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/64gb/"}]'
)
mock_send.return_value = fake_resp(fake_nodetype_list)
nodetypes = dockercloud.NodeType.list()
for i in range(0, len(nodetypes)):
result = json.loads(json.dumps(nodetypes[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_nodetype_fetch(self, mock_send):
attribute = json.loads(
'{"availability_zones": [], "available": true, "label": "8GB", "name": "8gb", "provider": "/api/v1/provider/digitalocean/", "regions": ["/api/v1/region/digitalocean/nyc1/", "/api/v1/region/digitalocean/ams1/", "/api/v1/region/digitalocean/sfo1/", "/api/v1/region/digitalocean/nyc2/", "/api/v1/region/digitalocean/ams2/", "/api/v1/region/digitalocean/sgp1/", "/api/v1/region/digitalocean/lon1/", "/api/v1/region/digitalocean/nyc3/", "/api/v1/region/digitalocean/ams3/"], "resource_uri": "/api/v1/nodetype/digitalocean/8gb/"}'
)
mock_send.return_value = fake_resp(fake_nodetype_fetch)
nodetype = dockercloud.NodeType.fetch('digitalocean/8gb')
result = json.loads(json.dumps(nodetype.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

52
tests/test_repository.py Normal file
View file

@ -0,0 +1,52 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class ImageTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_image_list(self, mock_send):
attributes = json.loads(
'[{"base_image": false, "categories": [], "cluster_aware": true, "description": "", "docker_registry": "/api/v1/registry/docker.com/", "image_url": "", "imagetag_set": ["/api/v1/image/docker.com/tifayuki/mongodb/tag/latest/"], "is_private_image": true, "name": "docker.com/tifayuki/mongodb", "public_url": "", "resource_uri": "/api/v1/image/docker.com/tifayuki/mongodb/", "starred": false}]'
)
mock_send.return_value = fake_resp(fake_image_list)
images = dockercloud.Repository.list()
for i in range(0, len(images)):
result = json.loads(json.dumps(images[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_image_fetch(self, mock_send):
attribute = json.loads(
'{"base_image": false, "categories": [], "cluster_aware": true, "description": "", "docker_registry": {"created": true, "host": "registry.hub.docker.com", "id": 5, "image_url": "/_static/assets/images/dockerregistries/docker.png", "is_ssl": true, "is_tutum_registry": false, "modified": true, "name": "Docker.io", "resource_uri": "/api/v1/registry/registry.hub.docker.com/", "uuid": "d533039e-c44c-4cdc-951b-e0e03b8410c6"}, "image_url": "", "imagetag_set": [{"full_name": "tifayuki/cadvisor:latest", "image": {"author": "fake <fake@docker.com>", "docker_id": "9e2907ef52bf811b4da100f50ba8f0908ccc610c7054bd69087f0a9f4703efdd", "entrypoint": "", "image_creation": "Fri, 15 Aug 2014 15:19:04 +0000", "imageenvvar_set": [{"key": "CADVISOR_TAG", "value": "0.2.2"}, {"key": "DB_NAME", "value": "cadvisor"}, {"key": "DB_PASS", "value": "root"}, {"key": "DB_USER", "value": "root"}, {"key": "HOME", "value": "/"}, {"key": "PATH", "value": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}], "imageport_set": [], "run_command": "/run.sh"}, "image_info": "/api/v1/image/tifayuki/cadvisor/", "name": "latest", "resource_uri": "/api/v1/image/tifayuki/cadvisor/tag/latest/"}], "is_private_image": true, "name": "tifayuki/cadvisor", "public_url": "https://registry.hub.docker.com/u/tifayuki/cadvisor/", "resource_uri": "/api/v1/image/tifayuki/cadvisor/", "starred": false}'
)
mock_send.return_value = fake_resp(fake_image_fetch)
image = dockercloud.Repository.fetch('tifayuki/cadvisor')
result = json.loads(json.dumps(image.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_image_save(self, mock_send):
attribute = json.loads(
'{"base_image": false, "categories": [], "cluster_aware": true, "description": "description", "docker_registry": {"created": true, "host": "registry.hub.docker.com", "id": 5, "image_url": "/_static/assets/images/dockerregistries/docker.png", "is_ssl": true, "is_tutum_registry": false, "modified": true, "name": "Docker.io", "resource_uri": "/api/v1/registry/registry.hub.docker.com/", "uuid": "d533039e-c44c-4cdc-951b-e0e03b8410c6"}, "image_url": "", "imagetag_set": [{"full_name": "tifayuki/cadvisor:latest", "image": {"author": "fake <fake@docker.com>", "docker_id": "9e2907ef52bf811b4da100f50ba8f0908ccc610c7054bd69087f0a9f4703efdd", "entrypoint": "", "image_creation": "Fri, 15 Aug 2014 15:19:04 +0000", "imageenvvar_set": [{"key": "CADVISOR_TAG", "value": "0.2.2"}, {"key": "DB_NAME", "value": "cadvisor"}, {"key": "DB_PASS", "value": "root"}, {"key": "DB_USER", "value": "root"}, {"key": "HOME", "value": "/"}, {"key": "PATH", "value": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}], "imageport_set": [], "run_command": "/run.sh"}, "image_info": "/api/v1/image/tifayuki/cadvisor/", "name": "latest", "resource_uri": "/api/v1/image/tifayuki/cadvisor/tag/latest/"}], "is_private_image": true, "name": "tifayuki/cadvisor", "public_url": "https://registry.hub.docker.com/u/tifayuki/cadvisor/", "resource_uri": "/api/v1/image/tifayuki/cadvisor/", "starred": false}'
)
mock_send.return_value = fake_resp(fake_image_save)
image = dockercloud.Repository.fetch('tifayuki/cadvisor')
image.description = 'descripiton'
self.assertTrue(image.save())
result = json.loads(json.dumps(image.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_image_delete(self, mock_send):
mock_send.side_effect = [fake_resp(fake_image_fetch), fake_resp(fake_image_delete)]
image = dockercloud.Repository.fetch('tifayuki/cadvisor')
self.assertTrue(image.delete())

93
tests/test_service.py Normal file
View file

@ -0,0 +1,93 @@
from __future__ import absolute_import
import unittest
import unittest.mock as mock
import dockercloud
from .fake_api import *
class ServiceTestCase(unittest.TestCase):
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_service_list(self, mock_send):
attributes = json.loads(
'[{"autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "cpu_shares": null, "current_num_containers": 3, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "state": "Running", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 3, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8"}]'
)
mock_send.return_value = fake_resp(fake_service_list)
services = dockercloud.Service.list()
for i in range(0, len(services)):
result = json.loads(json.dumps(services[i].get_all_attributes()))
target = json.loads(json.dumps(attributes[i]))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_service_fetch(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/e3ee01df-9f2f-4720-a114-ea1a236d47d2/", "/api/v1/action/c58213ab-8d5c-4a6d-b4c3-bd7157242dc2/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "containers": ["/api/v1/container/cff4dfa7-28a5-4599-a3f9-c7dc39353c11/", "/api/v1/container/4d966087-5169-4a0b-a2f0-78bbb878d872/", "/api/v1/container/0c84cd78-c239-40ad-939e-dbbc372ae345/"], "cpu_shares": null, "current_num_containers": 3, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "link_variables": {"HELLO_WORLD_1_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_1_ENV_HOME": "/", "HELLO_WORLD_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_1_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_1_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_1_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_2_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_2_ENV_HOME": "/", "HELLO_WORLD_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_2_PORT": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP_ADDR": "hello-world-2.a2f5a2e9-tifayuki.node.docker.io", "HELLO_WORLD_2_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_2_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_3_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_3_ENV_HOME": "/", "HELLO_WORLD_3_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_3_PORT": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP_ADDR": "hello-world-3.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_3_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_3_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_ENV_HOME": "/", "HELLO_WORLD_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "roles": [], "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "state": "Running", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 3, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8"}')
mock_send.return_value = fake_resp(fake_service_fetch)
service = dockercloud.Service.fetch('a2ac25c9-7cfe-4a1b-9d97-66de23642ee8')
result = json.loads(json.dumps(service.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_service_save(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/e3ee01df-9f2f-4720-a114-ea1a236d47d2/", "/api/v1/action/c58213ab-8d5c-4a6d-b4c3-bd7157242dc2/", "/api/v1/action/b3adfa79-dbd2-41a4-8c71-5e242ecce9bb/", "/api/v1/action/f82e25e7-d550-454e-a897-8599f2f530e5/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "containers": ["/api/v1/container/cff4dfa7-28a5-4599-a3f9-c7dc39353c11/", "/api/v1/container/4d966087-5169-4a0b-a2f0-78bbb878d872/", "/api/v1/container/0c84cd78-c239-40ad-939e-dbbc372ae345/"], "cpu_shares": null, "current_num_containers": 3, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "link_variables": {"HELLO_WORLD_1_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_1_ENV_HOME": "/", "HELLO_WORLD_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_1_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_1_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_1_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_2_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_2_ENV_HOME": "/", "HELLO_WORLD_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_2_PORT": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP_ADDR": "hello-world-2.a2f5a2e9-tifayuki.node.docker.io", "HELLO_WORLD_2_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_2_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_3_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_3_ENV_HOME": "/", "HELLO_WORLD_3_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_3_PORT": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP_ADDR": "hello-world-3.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_3_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_3_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_ENV_HOME": "/", "HELLO_WORLD_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "roles": [], "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "state": "Scaling", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 5, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8", "web_public_dns": ""}'
)
mock_send.side_effect = [fake_resp(fake_service_fetch), fake_resp(fake_service_save)]
service = dockercloud.Service.fetch('a2ac25c9-7cfe-4a1b-9d97-66de23642ee8')
service.target_num_containers = 5
self.assertTrue(service.save())
result = json.loads(json.dumps(service.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_service_delete(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/e3ee01df-9f2f-4720-a114-ea1a236d47d2/", "/api/v1/action/c58213ab-8d5c-4a6d-b4c3-bd7157242dc2/", "/api/v1/action/b3adfa79-dbd2-41a4-8c71-5e242ecce9bb/", "/api/v1/action/f82e25e7-d550-454e-a897-8599f2f530e5/", "/api/v1/action/381c53a0-bf18-4a01-a1b0-be87051c35e8/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 80, "outer_port": null, "port_name": "http", "protocol": "tcp", "uri_protocol": "http"}], "containers": ["/api/v1/container/cff4dfa7-28a5-4599-a3f9-c7dc39353c11/", "/api/v1/container/4d966087-5169-4a0b-a2f0-78bbb878d872/", "/api/v1/container/0c84cd78-c239-40ad-939e-dbbc372ae345/", "/api/v1/container/6e74df59-83ee-4351-8ba9-3d26e0d64c34/", "/api/v1/container/7bbff9f0-af41-408f-9dd0-213cd67e4aa2/"], "cpu_shares": null, "current_num_containers": 5, "deployed_datetime": "Tue, 30 Sep 2014 16:07:36 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/hello-world:latest", "image_tag": "/api/v1/image/tutum/hello-world/tag/latest/", "link_variables": {"HELLO_WORLD_1_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_1_ENV_HOME": "/", "HELLO_WORLD_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_1_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_1_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_1_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_1_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_2_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_2_ENV_HOME": "/", "HELLO_WORLD_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_2_PORT": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP": "tcp://hello-world-2.a2f5a2e9-tifayuki.node.docker.io:49155", "HELLO_WORLD_2_PORT_80_TCP_ADDR": "hello-world-2.a2f5a2e9-tifayuki.node.docker.io", "HELLO_WORLD_2_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_2_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_3_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_3_ENV_HOME": "/", "HELLO_WORLD_3_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_3_PORT": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP": "tcp://hello-world-3.5067d4f4-tifayuki.node.docker.io:49155", "HELLO_WORLD_3_PORT_80_TCP_ADDR": "hello-world-3.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_3_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_3_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_4_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_4_ENV_HOME": "/", "HELLO_WORLD_4_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_4_PORT": "tcp://hello-world-4.5067d4f4-tifayuki.node.docker.io:49156", "HELLO_WORLD_4_PORT_80_TCP": "tcp://hello-world-4.5067d4f4-tifayuki.node.docker.io:49156", "HELLO_WORLD_4_PORT_80_TCP_ADDR": "hello-world-4.5067d4f4-tifayuki.node.docker.io", "HELLO_WORLD_4_PORT_80_TCP_PORT": "49156", "HELLO_WORLD_4_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_5_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_5_ENV_HOME": "/", "HELLO_WORLD_5_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_5_PORT": "tcp://hello-world-5.fa9df19a-tifayuki.node.docker.io:49157", "HELLO_WORLD_5_PORT_80_TCP": "tcp://hello-world-5.fa9df19a-tifayuki.node.docker.io:49157", "HELLO_WORLD_5_PORT_80_TCP_ADDR": "hello-world-5.fa9df19a-tifayuki.node.docker.io", "HELLO_WORLD_5_PORT_80_TCP_PORT": "49157", "HELLO_WORLD_5_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_ENV_DEBIAN_FRONTEND": "noninteractive", "HELLO_WORLD_ENV_HOME": "/", "HELLO_WORLD_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HELLO_WORLD_PORT": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP": "tcp://hello-world-1.fa9df19a-tifayuki.node.docker.io:49155", "HELLO_WORLD_PORT_80_TCP_ADDR": "hello-world-1.fa9df19a-t* Connection #0 to host dashboard.docker.com left intactifayuki.node.docker.io", "HELLO_WORLD_PORT_80_TCP_PORT": "49155", "HELLO_WORLD_PORT_80_TCP_PROTO": "tcp", "HELLO_WORLD_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "hello-world", "resource_uri": "/api/v1/service/a2ac25c9-7cfe-4a1b-9d97-66de23642ee8/", "roles": [], "run_command": "/run.sh", "running_num_containers": 3, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 22:57:36 +0000", "state": "Terminating", "stopped_datetime": null, "stopped_num_containers": 0, "target_num_containers": 0, "unique_name": "hello-world", "uuid": "a2ac25c9-7cfe-4a1b-9d97-66de23642ee8"}'
)
mock_send.side_effect = [fake_resp(fake_service_fetch), fake_resp(fake_service_delete)]
service = dockercloud.Service.fetch('a2ac25c9-7cfe-4a1b-9d97-66de23642ee8')
self.assertTrue(service.delete())
result = json.loads(json.dumps(service.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_service_start(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/a8aaf64b-3186-41e7-9256-6b3d69786036/", "/api/v1/action/2481790d-a860-4bb4-95d2-5676ae2d6748/", "/api/v1/action/799a0d06-efae-4bf1-b063-7141f722cbb1/", "/api/v1/action/ef6b9f59-1edb-44c7-bcc6-1b80f737e4b0/", "/api/v1/action/99b7ac29-d16d-448f-bac5-54dbd5dd3b7b/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 3306, "outer_port": null, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "containers": ["/api/v1/container/2a1c4057-7753-4393-98c6-35699c198e08/", "/api/v1/container/54ead360-698f-4354-96f7-538f686cdd69/"], "cpu_shares": null, "current_num_containers": 2, "deployed_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "link_variables": {"MYSQL_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "mysql", "resource_uri": "/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/", "roles": [], "run_command": "/run.sh", "running_num_containers": 0, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "state": "Starting", "stopped_datetime": "Tue, 30 Sep 2014 23:09:09 +0000", "stopped_num_containers": 2, "target_num_containers": 2, "unique_name": "mysql", "uuid": "5ecde92d-498b-4bbb-b773-a998e5e421dc"}'
)
mock_send.side_effect = [fake_resp(fake_service_fetch), fake_resp(fake_service_start)]
service = dockercloud.Service.fetch('a2ac25c9-7cfe-4a1b-9d97-66de23642ee8')
self.assertTrue(service.start())
result = json.loads(json.dumps(service.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_service_stop(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/a8aaf64b-3186-41e7-9256-6b3d69786036/", "/api/v1/action/2481790d-a860-4bb4-95d2-5676ae2d6748/", "/api/v1/action/799a0d06-efae-4bf1-b063-7141f722cbb1/", "/api/v1/action/ef6b9f59-1edb-44c7-bcc6-1b80f737e4b0/", "/api/v1/action/99b7ac29-d16d-448f-bac5-54dbd5dd3b7b/", "/api/v1/action/98816bfc-4fa7-4697-bea1-d926de69b48f/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 3306, "outer_port": null, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "containers": ["/api/v1/container/2a1c4057-7753-4393-98c6-35699c198e08/", "/api/v1/container/54ead360-698f-4354-96f7-538f686cdd69/"], "cpu_shares": null, "current_num_containers": 2, "deployed_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "link_variables": {"MYSQL_1_ENV_DEBIAN_FRONTEND": "noninteractive", "MYSQL_1_ENV_MYSQL_PASS": "**Random**", "MYSQL_1_ENV_MYSQL_USER": "admin", "MYSQL_1_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "MYSQL_1_ENV_REPLICATION_MASTER": "**False**", "MYSQL_1_ENV_REPLICATION_PASS": "replica", "MYSQL_1_ENV_REPLICATION_SLAVE": "**False**", "MYSQL_1_ENV_REPLICATION_USER": "replica", "MYSQL_1_PORT": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_1_PORT_3306_TCP": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_1_PORT_3306_TCP_ADDR": "mysql-1.fa9df19a-tifayuki.node.docker.io", "MYSQL_1_PORT_3306_TCP_PORT": "49156", "MYSQL_1_PORT_3306_TCP_PROTO": "tcp", "MYSQL_2_ENV_DEBIAN_FRONTEND": "noninteractive", "MYSQL_2_ENV_MYSQL_PASS": "**Random**", "MYSQL_2_ENV_MYSQL_USER": "admin", "MYSQL_2_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "MYSQL_2_ENV_REPLICATION_MASTER": "**False**", "MYSQL_2_ENV_REPLICATION_PASS": "replica", "MYSQL_2_ENV_REPLICATION_SLAVE": "**False**", "MYSQL_2_ENV_REPLICATION_USER": "replica", "MYSQL_2_PORT": "tcp://mysql-2.a2f5a2e9-tifayuki.node.docker.io:49156", "MYSQL_2_PORT_3306_TCP": "tcp://mysql-2.a2f5a2e9-tifayuki.node.docker.io:49156", "MYSQL_2_PORT_3306_TCP_ADDR": "mysql-2.a2f5a2e9-tifayuki.node.docker.io", "MYSQL_2_PORT_3306_TCP_PORT": "49156", "MYSQL_2_PORT_3306_TCP_PROTO": "tcp", "MYSQL_ENV_DEBIAN_FRONTEND": "noninteractive", "MYSQL_ENV_MYSQL_PASS": "**Random**", "MYSQL_ENV_MYSQL_USER": "admin", "MYSQL_ENV_PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "MYSQL_ENV_REPLICATION_MASTER": "**False**", "MYSQL_ENV_REPLICATION_PASS": "replica", "MYSQL_ENV_REPLICATION_SLAVE": "**False**", "MYSQL_ENV_REPLICATION_USER": "replica", "MYSQL_PORT": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_PORT_3306_TCP": "tcp://mysql-1.fa9df19a-tifayuki.node.docker.io:49156", "MYSQL_PORT_3306_TCP_ADDR": "mysql-1.fa9df19a-tifayuki.node.docker.io", "MYSQL_PORT_3306_TCP_PORT": "49156", "MYSQL_PORT_3306_TCP_PROTO": "tcp", "MYSQL_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "mysql", "resource_uri": "/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/", "roles": [], "run_command": "/run.sh", "running_num_containers": 1, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 23:50:59 +0000", "state": "Stopping", "stopped_datetime": "Tue, 30 Sep 2014 23:09:09 +0000", "stopped_num_containers": 0, "target_num_containers": 2, "unique_name": "mysql", "uuid": "5ecde92d-498b-4bbb-b773-a998e5e421dc"}'
)
mock_send.side_effect = [fake_resp(fake_service_fetch), fake_resp(fake_service_stop)]
service = dockercloud.Service.fetch('a2ac25c9-7cfe-4a1b-9d97-66de23642ee8')
self.assertTrue(service.stop())
result = json.loads(json.dumps(service.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)
@mock.patch.object(dockercloud.api.http.Session, 'send')
def test_service_redeploy(self, mock_send):
attribute = json.loads(
'{"actions": ["/api/v1/action/a8aaf64b-3186-41e7-9256-6b3d69786036/", "/api/v1/action/2481790d-a860-4bb4-95d2-5676ae2d6748/", "/api/v1/action/799a0d06-efae-4bf1-b063-7141f722cbb1/", "/api/v1/action/ef6b9f59-1edb-44c7-bcc6-1b80f737e4b0/", "/api/v1/action/99b7ac29-d16d-448f-bac5-54dbd5dd3b7b/", "/api/v1/action/98816bfc-4fa7-4697-bea1-d926de69b48f/", "/api/v1/action/285e250f-7429-4f0a-a252-5594a05c8020/"], "autodestroy": "OFF", "autoreplace": "OFF", "autorestart": "OFF", "container_envvars": [], "container_ports": [{"endpoint_uri": null, "inner_port": 3306, "outer_port": null, "port_name": "mysql", "protocol": "tcp", "uri_protocol": "mysql"}], "containers": ["/api/v1/container/2a1c4057-7753-4393-98c6-35699c198e08/", "/api/v1/container/54ead360-698f-4354-96f7-538f686cdd69/"], "cpu_shares": null, "current_num_containers": 2, "deployed_datetime": "Tue, 30 Sep 2014 22:44:44 +0000", "destroyed_datetime": null, "entrypoint": "", "image_name": "tutum/mysql:latest", "image_tag": "/api/v1/image/tutum/mysql/tag/latest/", "link_variables": {"MYSQL_TUTUM_API_URL": "https://dashboard.docker.com/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/"}, "linked_from_service": [], "linked_to_service": [], "memory": null, "memory_swap": null, "name": "mysql", "resource_uri": "/api/v1/service/5ecde92d-498b-4bbb-b773-a998e5e421dc/", "roles": [], "run_command": "/run.sh", "running_num_containers": 0, "sequential_deployment": false, "started_datetime": "Tue, 30 Sep 2014 23:50:59 +0000", "state": "Redeploying", "stopped_datetime": "Tue, 30 Sep 2014 23:51:52 +0000", "stopped_num_containers": 0, "target_num_containers": 2, "unique_name": "mysql", "uuid": "5ecde92d-498b-4bbb-b773-a998e5e421dc"}'
)
mock_send.side_effect = [fake_resp(fake_service_fetch), fake_resp(fake_service_redeploy)]
service = dockercloud.Service.fetch('a2ac25c9-7cfe-4a1b-9d97-66de23642ee8')
self.assertTrue(service.redeploy())
result = json.loads(json.dumps(service.get_all_attributes()))
target = json.loads(json.dumps(attribute))
self.assertDictEqual(target, result)

221
tests/test_utils.py Normal file
View file

@ -0,0 +1,221 @@
import unittest
import unittest.mock as mock
import dockercloud
from dockercloud.api.exceptions import ObjectNotFound, ApiError, NonUniqueIdentifier
class FetchRemoteObjectTestCase(unittest.TestCase):
@mock.patch('dockercloud.Container.list')
@mock.patch('dockercloud.Container.fetch')
def test_fetch_remote_container(self, mock_fetch, mock_list):
# test container exist queried with uuid4
mock_fetch.return_value = 'returned'
self.assertEqual(dockercloud.Utils.fetch_remote_container('7A4CFE51-03BB-42D6-825E-3B533888D8CD', True),
'returned')
self.assertEqual(dockercloud.Utils.fetch_remote_container('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False),
'returned')
# test container doesn't exist queried with uuid4
mock_fetch.side_effect = ObjectNotFound
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_container,
'7A4CFE51-03BB-42D6-825E-3B533888D8CD',
True)
self.assertIsInstance(dockercloud.Utils.fetch_remote_container('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False),
ObjectNotFound)
# test unique container found queried with short uuid
container = dockercloud.Container.create()
container.uuid = 'uuid'
mock_list.side_effect = [[container], []]
mock_fetch.side_effect = [container]
self.assertEquals(dockercloud.Utils.fetch_remote_container('shortuuid', True), container)
mock_list.side_effect = [[container], []]
mock_fetch.side_effect = [container]
self.assertEquals(dockercloud.Utils.fetch_remote_container('shortuuid', False), container)
# test unique container found queried with name
mock_list.side_effect = [[], [container]]
mock_fetch.side_effect = [container]
self.assertEquals(dockercloud.Utils.fetch_remote_container('name', True), container)
mock_list.side_effect = [[], [container]]
mock_fetch.side_effect = [container]
self.assertEquals(dockercloud.Utils.fetch_remote_container('name', False), container)
# test no container found
mock_list.side_effect = [[], []]
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_container, 'uuid_or_name', True)
mock_list.side_effect = [[], []]
self.assertIsInstance(dockercloud.Utils.fetch_remote_container('uuid_or_name', False), ObjectNotFound)
# test multi-container found
mock_list.side_effect = [['container1', 'container2'], []]
self.assertRaises(NonUniqueIdentifier, dockercloud.Utils.fetch_remote_container, 'uuid_or_name', True)
mock_list.side_effect = [['container1', 'container2'], []]
self.assertIsInstance(dockercloud.Utils.fetch_remote_container('uuid_or_name', False), NonUniqueIdentifier)
mock_list.side_effect = [[], ['container1', 'container2']]
self.assertRaises(NonUniqueIdentifier, dockercloud.Utils.fetch_remote_container, 'uuid_or_name', True)
mock_list.side_effect = [[], ['container1', 'container2']]
self.assertIsInstance(dockercloud.Utils.fetch_remote_container('uuid_or_name', False), NonUniqueIdentifier)
# test api error
mock_list.side_effect = [ApiError, ApiError]
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_container, 'uuid_or_name', True)
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_container, 'uuid_or_name', False)
@mock.patch('dockercloud.Service.list')
@mock.patch('dockercloud.Service.fetch')
def test_fetch_remote_service(self, mock_fetch, mock_list):
# test cluster exist queried with uuid4
mock_fetch.return_value = 'returned'
self.assertEqual(dockercloud.Utils.fetch_remote_service('7A4CFE51-03BB-42D6-825E-3B533888D8CD', True),
'returned')
self.assertEqual(dockercloud.Utils.fetch_remote_service('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False),
'returned')
# test cluster doesn't exist queried with uuid4
mock_fetch.side_effect = ObjectNotFound
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_service,
'7A4CFE51-03BB-42D6-825E-3B533888D8CD',
True)
self.assertIsInstance(dockercloud.Utils.fetch_remote_service('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False),
ObjectNotFound)
# test unique cluster found queried with short uuid
service = dockercloud.Service.create()
service.uuid = 'uuid'
mock_list.side_effect = [[service], []]
mock_fetch.side_effect = [service]
self.assertEquals(dockercloud.Utils.fetch_remote_service('shortuuid', True), service)
mock_list.side_effect = [[service], []]
mock_fetch.side_effect = [service]
self.assertEquals(dockercloud.Utils.fetch_remote_service('shortuuid', False), service)
# test unique cluster found queried with name
mock_list.side_effect = [[], [service]]
mock_fetch.side_effect = [service]
self.assertEquals(dockercloud.Utils.fetch_remote_service('name', True), service)
mock_list.side_effect = [[], [service]]
mock_fetch.side_effect = [service]
self.assertEquals(dockercloud.Utils.fetch_remote_service('name', False), service)
# test no cluster found
mock_list.side_effect = [[], []]
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_service, 'uuid_or_name', True)
mock_list.side_effect = [[], []]
self.assertIsInstance(dockercloud.Utils.fetch_remote_service('uuid_or_name', False), ObjectNotFound)
# test multi-cluster found
mock_list.side_effect = [['cluster1', 'cluster2'], []]
self.assertRaises(NonUniqueIdentifier, dockercloud.Utils.fetch_remote_service, 'uuid_or_name', True)
mock_list.side_effect = [['cluster1', 'cluster2'], []]
self.assertIsInstance(dockercloud.Utils.fetch_remote_service('uuid_or_name', False), NonUniqueIdentifier)
mock_list.side_effect = [[], ['cluster1', 'cluster2']]
self.assertRaises(NonUniqueIdentifier, dockercloud.Utils.fetch_remote_service, 'uuid_or_name', True)
mock_list.side_effect = [[], ['cluster1', 'cluster2']]
self.assertIsInstance(dockercloud.Utils.fetch_remote_service('uuid_or_name', False), NonUniqueIdentifier)
# test api error
mock_list.side_effect = [ApiError, ApiError]
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_service, 'uuid_or_name', True)
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_service, 'uuid_or_name', False)
@mock.patch('dockercloud.Node.list')
@mock.patch('dockercloud.Node.fetch')
def test_fetch_remote_node(self, mock_fetch, mock_list):
# test node exist queried with uuid4
mock_fetch.return_value = 'returned'
self.assertEqual(dockercloud.Utils.fetch_remote_node('7A4CFE51-03BB-42D6-825E-3B533888D8CD', True), 'returned')
self.assertEqual(dockercloud.Utils.fetch_remote_node('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False), 'returned')
# test node doesn't exist queried with uuid4
mock_fetch.side_effect = ObjectNotFound
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_node, '7A4CFE51-03BB-42D6-825E-3B533888D8CD',
True)
self.assertIsInstance(dockercloud.Utils.fetch_remote_node('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False),
ObjectNotFound)
# test unique node found queried with short uuid
node = dockercloud.Node.create()
node.uuid = 'uuid'
mock_list.side_effect = [[node]]
mock_fetch.side_effect = [node]
self.assertEquals(dockercloud.Utils.fetch_remote_node('uuid', True), node)
mock_list.side_effect = [[node]]
mock_fetch.side_effect = [node]
self.assertEquals(dockercloud.Utils.fetch_remote_node('uuid', False), node)
# test no node found
mock_list.side_effect = [[]]
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_node, 'uuid', True)
mock_list.side_effect = [[]]
self.assertIsInstance(dockercloud.Utils.fetch_remote_node('uuid', False), ObjectNotFound)
# test multi-node found
mock_list.side_effect = [['node1', 'node2']]
self.assertRaises(NonUniqueIdentifier, dockercloud.Utils.fetch_remote_node, 'uuid', True)
mock_list.side_effect = [['node1', 'node2']]
self.assertIsInstance(dockercloud.Utils.fetch_remote_node('uuid', False), NonUniqueIdentifier)
# test api error
mock_list.side_effect = [ApiError, ApiError]
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_node, 'uuid', True)
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_node, 'uuid', False)
@mock.patch('dockercloud.NodeCluster.list')
@mock.patch('dockercloud.NodeCluster.fetch')
def test_fetch_remote_nodecluster(self, mock_fetch, mock_list):
# test nodecluster exist queried with uuid4
mock_fetch.return_value = 'returned'
self.assertEqual(dockercloud.Utils.fetch_remote_nodecluster('7A4CFE51-03BB-42D6-825E-3B533888D8CD', True),
'returned')
self.assertEqual(dockercloud.Utils.fetch_remote_nodecluster('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False),
'returned')
# test nodecluster doesn't exist queried with uuid4
mock_fetch.side_effect = ObjectNotFound
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_nodecluster,
'7A4CFE51-03BB-42D6-825E-3B533888D8CD',
True)
self.assertIsInstance(dockercloud.Utils.fetch_remote_nodecluster('7A4CFE51-03BB-42D6-825E-3B533888D8CD', False),
ObjectNotFound)
# test unique nodecluster found queried with short uuid
nodecluster = dockercloud.NodeCluster.create()
nodecluster.uuid = 'uuid'
mock_list.side_effect = [[nodecluster], []]
mock_fetch.side_effect = [nodecluster]
self.assertEquals(dockercloud.Utils.fetch_remote_nodecluster('shortuuid', True), nodecluster)
mock_list.side_effect = [[nodecluster], []]
mock_fetch.side_effect = [nodecluster]
self.assertEquals(dockercloud.Utils.fetch_remote_nodecluster('shortuuid', False), nodecluster)
# test unique nodecluster found queried with name
mock_list.side_effect = [[], [nodecluster]]
mock_fetch.side_effect = [nodecluster]
self.assertEquals(dockercloud.Utils.fetch_remote_nodecluster('name', True), nodecluster)
mock_list.side_effect = [[], [nodecluster]]
mock_fetch.side_effect = [nodecluster]
self.assertEquals(dockercloud.Utils.fetch_remote_nodecluster('name', False), nodecluster)
# test no nodecluster found
mock_list.side_effect = [[], []]
self.assertRaises(ObjectNotFound, dockercloud.Utils.fetch_remote_nodecluster, 'uuid_or_name', True)
mock_list.side_effect = [[], []]
self.assertIsInstance(dockercloud.Utils.fetch_remote_nodecluster('uuid_or_name', False), ObjectNotFound)
# test multi-nodecluster found
mock_list.side_effect = [['nodecluster1', 'nodecluster2'], []]
self.assertRaises(NonUniqueIdentifier, dockercloud.Utils.fetch_remote_nodecluster, 'uuid_or_name', True)
mock_list.side_effect = [['nodecluster1', 'nodecluster2'], []]
self.assertIsInstance(dockercloud.Utils.fetch_remote_nodecluster('uuid_or_name', False), NonUniqueIdentifier)
mock_list.side_effect = [[], ['nodecluster1', 'nodecluster2']]
self.assertRaises(NonUniqueIdentifier, dockercloud.Utils.fetch_remote_nodecluster, 'uuid_or_name', True)
mock_list.side_effect = [[], ['nodecluster1', 'nodecluster2']]
self.assertIsInstance(dockercloud.Utils.fetch_remote_nodecluster('uuid_or_name', False), NonUniqueIdentifier)
# test api error
mock_list.side_effect = [ApiError, ApiError]
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_nodecluster, 'uuid_or_name', True)
self.assertRaises(ApiError, dockercloud.Utils.fetch_remote_nodecluster, 'uuid_or_name', False)