diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c922f11 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include public * diff --git a/bugs/fixed_bugs.txt b/bugs/fixed_bugs.txt new file mode 100644 index 0000000..2bf4cbb --- /dev/null +++ b/bugs/fixed_bugs.txt @@ -0,0 +1,3 @@ +================================================================= +Add fixed bugs in this file. Select a format to add bugs. +================================================================= diff --git a/bugs/implemented_blueprints.txt b/bugs/implemented_blueprints.txt new file mode 100644 index 0000000..a472e29 --- /dev/null +++ b/bugs/implemented_blueprints.txt @@ -0,0 +1,3 @@ +======================================================================== +Add already implemented. +======================================================================== diff --git a/bugs/known_bugs.txt b/bugs/known_bugs.txt new file mode 100644 index 0000000..d4fa5dc --- /dev/null +++ b/bugs/known_bugs.txt @@ -0,0 +1,15 @@ +================================================================= +Add known bugs in this file. Select a format to add bugs. +================================================================= +1) Add support for different communication mechnism between api server + and engine. + --> Message queue + --> API server itself + +2) Create /var/run/netns at the start of the dockyard. +3) Add database feature to keep data even after reboot. +4) Add feature to create resource as dockyard start. +5) Write a single function, which will load modules dynmically by taking + information from the configuration file. It is being done many times in + the project which will lead to resuability of the code. +6) Create an API for attaching external network to the docker container diff --git a/bugs/new_blueprints.txt b/bugs/new_blueprints.txt new file mode 100644 index 0000000..b4c9b06 --- /dev/null +++ b/bugs/new_blueprints.txt @@ -0,0 +1,4 @@ +======================================================================== +Add blueprints that needs to implmented. +======================================================================== +1. Monitoring of containers. User can specify which container to monitor. The monitoring module would monitor the containers and start the stopped container. diff --git a/dockyard/__init__.py b/dockyard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/api/__init__.py b/dockyard/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/api/app.py b/dockyard/api/app.py new file mode 100644 index 0000000..318e230 --- /dev/null +++ b/dockyard/api/app.py @@ -0,0 +1,44 @@ +from oslo_config import cfg +import pecan +from pecan import make_app + +from dockyard.api import config as api_config +# To be used later for initialization of database +#from dockyard import model + + +API_SERVICE_OPT = [ + cfg.PortOpt('port', + default=5869, + help='Port for the dockyard service.'), + cfg.IPOpt('host', + default='0.0.0.0', + help='Listening address for dockyard service'), +] + +CONF = cfg.CONF +opt_group = cfg.OptGroup(name='default', + title='Group for the default values of dockyard api') +CONF.register_group(opt_group) +CONF.register_opts(API_SERVICE_OPT, opt_group) + + +def get_pecan_config(): + # Set up the pecan configuration + filename = api_config.__file__.replace('.pyc', '.py') + return pecan.configuration.conf_from_file(filename) + + +def setup_app(config=None): + if not config: + config = get_pecan_config() + + # To be used later for initialization of database + # model.init_model() + app_conf = dict(config.app) + + return make_app( + app_conf.pop('root'), + logging=getattr(config, 'logging', {}), + **app_conf + ) diff --git a/dockyard/api/config.py b/dockyard/api/config.py new file mode 100644 index 0000000..dabf7d8 --- /dev/null +++ b/dockyard/api/config.py @@ -0,0 +1,6 @@ +# Pecan Application Configurations +app = { + 'root': 'dockyard.controllers.root.RootController', + 'modules': ['dockyard', 'dockyard.api'], + 'debug': True +} diff --git a/dockyard/cmd/__init__.py b/dockyard/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/cmd/api.py b/dockyard/cmd/api.py new file mode 100644 index 0000000..8f11839 --- /dev/null +++ b/dockyard/cmd/api.py @@ -0,0 +1,31 @@ +import os +import sys + +from oslo_config import cfg +from oslo_log import log as logging +from wsgiref import simple_server + +from dockyard.api import app as api_app +from dockyard.common import utils + + +LOG = logging.getLogger(__name__) + +def main(): + utils.prepare_logging(sys.argv) + + app = api_app.setup_app() + + # create the wsgi server and start it + host, port = cfg.CONF.default.host, cfg.CONF.default.port + srv = simple_server.make_server(host, port, app) + + LOG.info('Starting dockyard in PID %s', os.getpid()) + LOG.debug("Configuration:") + cfg.CONF.log_opt_values(LOG, logging.DEBUG) + + LOG.info('serving at http://%s:%s' % (host, port)) + srv.serve_forever() + + +main() diff --git a/dockyard/common/__init__.py b/dockyard/common/__init__.py new file mode 100644 index 0000000..46ae25d --- /dev/null +++ b/dockyard/common/__init__.py @@ -0,0 +1,106 @@ +import ast +import importlib +from oslo_config import cfg +from threading import Thread +import time +from dockyard.common.container.container import Container +from dockyard.common.image.image import Image +from dockyard.common.network.network import Network +from dockyard.common.volume.volume import Volume +from dockyard.common.utils import get_localhost_ip +from dockyard.engine_client.synchronizer.synchronizer import ( + ContainerSynchronizer) + +CONF = cfg.CONF + + +class Synchronizer(Thread): + def __init__(self): + if CONF.docker.docker_host == "0.0.0.0": + for ip in get_localhost_ip(): + host = ip + break + else: + host = CONF.docker.docker_host + + port = CONF.docker.docker_port + self.host = { 'host': host, 'port': port } + Thread.__init__(self) + self.container = Container() + self.image = Image() + self.network = Network() + self.volume = Volume() + self.synchronizer = ContainerSynchronizer() + self.sleep_time = CONF.database.synchronization_time + + + def run(self): + """This thread is responsible for synchronizations of containers, + and other docker resources. + """ + while True: + self._synchronize() + time.sleep(self.sleep_time) + + def _get_format(self, value, type_): + """This method converts a container information into the required format + for the container. + """ + f_val = dict() + for v in value: + f_val[v] = "OK" + + value = {type_: f_val} + key = (self.host["host"], type_) + return (key, value) + + def _synchronize_container(self): + containers = self.container.list(host=self.host) + return self._sync(containers, type_="container") + + def _synchronize_image(self): + images = self.image.list(host=self.host) + return self._sync(images, type_="image") + + def _synchronize_network(self): + networks = self.network.list(host=self.host) + return self._sync(networks, type_="network") + + def _synchronize_volume(self): + volumes = self.container.list(host=self.host) + return self._sync(volumes, type_="volume") + + def _get_ids(self, info_s): + """This method returns all the values ids from the list of + dictionary received. + """ + ids = [x["Id"] for x in info_s] + return ids + + def _sync(self, info_s, type_=None): + info_s = info_s.replace("null", "None") + info_s = info_s.replace("true", "True") + info_s = info_s.replace("false", "False") + info_s = ast.literal_eval(info_s) + info_s = self._get_ids(info_s) + info_s = self._get_format(value=info_s, type_=type_) + self.synchronizer.synchronize([info_s]) + + def _synchronize(self): + """This method fetch all the containers running on local machines. + """ + self._synchronize_container() + self._synchronize_volume() + self._synchronize_image() + self._synchronize_network() + + +def run_synchronizer(): + """This method is responsible for synchronizing containers information + with the consul databse. + """ + sync = Synchronizer() + sync.setName("Synchronizer") + sync.start() + +run_synchronizer() diff --git a/dockyard/common/base.py b/dockyard/common/base.py new file mode 100644 index 0000000..c80dc0e --- /dev/null +++ b/dockyard/common/base.py @@ -0,0 +1,77 @@ +import abc +import importlib +from oslo_config import cfg +from pecan import request +from urllib3 import PoolManager + +ENGINE_CLIENT_OPT = [ + cfg.StrOpt('engine_client', + default='api_server.api_server.APIServerEngineClient', + help='Client to be used for sending request to engine') +] + +CONF = cfg.CONF +CONF.register_opts(ENGINE_CLIENT_OPT, group='default') + +# Fetch scheduler defined in the configuration file and load it. +engine_client_info = CONF.default.engine_client +# May be this path can be specified in the configuration file. +engine_client_loc = 'dockyard.engine_client' +engine_client_info = (('%s.%s') % (engine_client_loc, engine_client_info)) +module_name, class_name = engine_client_info.rsplit(".", 1) +engine_client = getattr(importlib.import_module(module_name), class_name) + +class URL(object): + def __init__(self): + pass + + @abc.abstractmethod + def send(self, method, url, headers=None, post_params=None, + body=None, **kwargs): + """This methos is responsible for sending the request. + :method: Method to be used for sending request. + :url: URL to be send. + :headers: headers in the requests. + :post_params: post parameters. + :body: Request body. + """ + +class DockyardURL(URL): + def __init__(self): + self.engine_client = engine_client() + self.pool = PoolManager() + + def _is_local_request(self): + """This method checks whether this is request for docker engine. + """ + try: + request.headers.environ['Request-Status'] + except KeyError: + status = False + except: + status = True + + return status + + def send(self, method, url, headers=None, post_params=None, + body=None, **kwargs): + """This methos is responsible for sending the request. + :method: Method to be used for sending request. + :url: URL to be send. + :headers: headers in the requests. + :post_params: post parameters. + :body: Request body. + """ + req_type = self._is_local_request() + # Pre processing needs to be done for dockyard feature before + # performing some actions + if req_type: + self.engine_client.process(method=method, url=url, r_type="pre", + body=body, headers=headers) + + data = self.pool.urlopen(method, url, headers=headers, body=body).data + # Post processing needs to be done for some of the dockyard operations + if req_type: + self.engine_client.process(method=method, url=url, r_type="post", + body=body, headers=headers) + return data diff --git a/dockyard/common/cluster/__init__.py b/dockyard/common/cluster/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/cluster/cluster.py b/dockyard/common/cluster/cluster.py new file mode 100644 index 0000000..5f72747 --- /dev/null +++ b/dockyard/common/cluster/cluster.py @@ -0,0 +1,17 @@ +from oslo_config import cfg +from oslo_log import log as logging + + +class Cluster(object): + def __init__(self): + pass + + def register(self, cluster_id, host_ip, port): + pass + + def unregister(self, cluster_id, host_ip): + pass + + def get_hosts(self): + LOG.debug("Registered hosts: %s" % cfg.CONF.membership.hosts) + return cfg.CONF.membership.hosts diff --git a/dockyard/common/cluster/membership/__init__.py b/dockyard/common/cluster/membership/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/container/__init__.py b/dockyard/common/container/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/container/container.py b/dockyard/common/container/container.py new file mode 100644 index 0000000..0f09128 --- /dev/null +++ b/dockyard/common/container/container.py @@ -0,0 +1,140 @@ +from oslo_log import log as logging + +from dockyard.common import utils, link +from dockyard.common import url + +import json +from pecan.core import redirect + +LOG = logging.getLogger(__name__) + +class Container(object): + base_url = '/containers' + + def __init__(self): + self.url = url.URL(self.base_url) + + def list(self, name_or_id=None, query_params=None, host=None): + url_ = self.url.make_url(url_='json', id_=name_or_id) + + msg = ("List containers with url: %s " + "query_params: %s" % + (url_, query_params)) + + LOG.debug(msg) + + return utils.dispatch_get_request(url_, query_params=query_params, + host=host) + + def changes(self, name_or_id=None, host=None): + url_ = self.url.make_url(url_='changes', id_=name_or_id) + return utils.dispatch_get_request(url_, host=host) + + def resize(self, id_, host=None, **kwargs): + url_ = self.url.make_url(url_='resize', id_=id_) + + query = '' + if kwargs: + query = link.make_query_url(kwargs) + + return utils.dispatch_get_request(url_, query_params=query, host=host) + + def export(self, name_or_id=None, host=None): + url_ = self.url.make_url(url_='export', id_=name_or_id) + redirect(utils.get_link(url_)) + + def top(self, name_or_id=None, query_params=None, host=None): + url_ = self.url.make_url(url_='top', id_=name_or_id) + return utils.dispatch_get_request(url_, query_params=query_params, + host=host) + + def stats(self, id_, host=None): + url_ = self.url.make_url(url_='stats', id_=id_) + redirect(utils.get_link(url_)) + + def archive(self, id_, query_params=None, host=None): + url_ = self.url.make_url(url_='archive', id_=id_) + return utils.dispatch_put_request(url_, query_params=query_params, + host=host) + + def create(self, body=None, host=None): + url_ = self.url.make_url(url_='create') + return utils.dispatch_post_request(url_, body=json.dumps(body), + host=host) + + def upload(self, id_, body=None, query=None, host=None, **kwargs): + url_ = self.url.make_url(url_='archive', id_=id_) + return utils.dispatch_put_request(url_, body=body, query_params=query, + host=host) + + def copy(self, id_, host=None): + url_ = self.url.make_url(url_='copy', id_=id_) + redirect(utils.get_link(url_, host=host)) + + def logs(self, id_, query_string, host=None, **kwargs): + url_ = self.url.make_url(url_='logs', id_=id_) + + query = '' + if kwargs: + query = link.make_query_url(kwargs) + + redirect(utils.get_link(url_, query_params=query)) + + def start(self, id_, query_params=None, host=None): + url_ = self.url.make_url(url_='start', id_=id_) + return utils.dispatch_post_request(url_, query_params=query_params, + host=host) + + def restart(self, id_, query_params=None, host=None): + url_ = self.url.make_url(url_='restart', id_=id_) + return utils.dispatch_post_request(url_, query_params=query_params, + host=host) + + def kill(self, id_, query_params=None, host=None): + url_ = self.url.make_url(url_='kill', id_=id_) + return utils.dispatch_post_request(url_, query_params=query_params, host=host) + + def stop(self, id_, query_params=None, host=None): + url_ = self.url.make_url(url_='stop', id_=id_) + return utils.dispatch_post_request(url_, query_params=query_params, host=host) + + def exe(self, _id): + abort(404) + + def attach(self, id_, query_params=None, host=None): + url_ = self.url.make_url(url_='attach', id_=id_) + redirect(utils.get_link(url_, quer_params=query_params, host=host)) + + def rename(self, id_, host=None, **kwargs): + url_ = self.url.make_url(url_='rename', id_=id_) + + query = '' + if kwargs: + query = link.make_query_url(kwargs) + + return utils.dispatch_post_request(url_, query_params=query, host=host) + + def update(self, id_, body=None, host=None): + url_ = self.url.make_url(url_='update', id_=id_) + return utils.dispatch_post_request(url_, body=json.dumps(body), host=host) + + def pause(self, id_, host=None): + url_ = self.url.make_url(url_='pause', id_=id_) + return utils.dispatch_post_request(url_, host=host) + + def unpause(self, id_, host=None): + url_ = self.url.make_url(url_='unpause', id_=id_) + return utils.dispatch_post_request(url_, host=host) + + def wait(self, id_, host=None): + url_ = self.url.make_url(url_='wait', id_=id_) + return utils.dispatch_post_request(url_, host=host) + + def delete(self, id_, host=None, **kwargs): + url_ = self.url.make_url(id_=id_) + + query = '' + if kwargs: + query = link.make_query_url(kwargs) + + return utils.dispatch_delete_request(url_, query_params=query, host=host) diff --git a/dockyard/common/container/scheduler/__init__.py b/dockyard/common/container/scheduler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/container/scheduler/base.py b/dockyard/common/container/scheduler/base.py new file mode 100644 index 0000000..09d05f7 --- /dev/null +++ b/dockyard/common/container/scheduler/base.py @@ -0,0 +1,15 @@ +import abc + + +class Scheduler(object): + @abc.abstractmethod + def get_host(self, *args, **kwargs): + """This method returns host information to launch + container. + + It expects specification of docker container and + interact with membership managment protocol to + find the host to run a container. + + Every scheduler should have this method. + """ diff --git a/dockyard/common/container/scheduler/round_robin.py b/dockyard/common/container/scheduler/round_robin.py new file mode 100644 index 0000000..99a895e --- /dev/null +++ b/dockyard/common/container/scheduler/round_robin.py @@ -0,0 +1,15 @@ +from oslo_config import cfg + +from dockyard.common.container.scheduler.base import Scheduler + + +class RoundRobinScheduler(Scheduler): + count = -1 + + def __init__(self): + pass + + def get_host(self, hosts): + num_hosts = len(hosts) + self.count = (self.count + 1) % num_hosts + return (hosts[self.count]) diff --git a/dockyard/common/exception.py b/dockyard/common/exception.py new file mode 100644 index 0000000..e567321 --- /dev/null +++ b/dockyard/common/exception.py @@ -0,0 +1,18 @@ +class DockyardException(Exception): + def __init__(self, message, **kwargs): + if message: + self.message = message + + try: + self.message = self.message % message + except Exception as e: + pass + + super(DockyardException, self).__init__(self.message) + + +class IncompleteInfo(DockyardException): + message = ("%s is missing. Incomplete information.") + +class NoValidHostFound(DockyardException): + message = ("No Valid host for %s is found") diff --git a/dockyard/common/image/__init__.py b/dockyard/common/image/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/image/image.py b/dockyard/common/image/image.py new file mode 100644 index 0000000..e1bcb17 --- /dev/null +++ b/dockyard/common/image/image.py @@ -0,0 +1,42 @@ +from pecan import abort +from dockyard.common import link +from dockyard.common import utils, url + + +class Image(object): + base_url = '/images' + + def __init__(self): + self.url = url.URL(self.base_url) + + def list(self, id_=None, host=None): + url_ = self.url.make_url(url_='json', id_=id_) + return utils.dispatch_get_request(url=url_, host=host) + + def history(self, id_=None, host=None): + url_ = self.url.make_url(url_='history', id_=id_) + return utils.dispatch_get_request(url=url_, host=host) + + def search(self, term=None, host=None): + url_ = self.url.make_url(url_='search') + query = {"term": term} + return utils.dispatch_get_request(url=url_, query_params=query, + host=host) + + def create(self, fromImage, tag, host=None): + url_ = self.url.make_url(url_='create') + query = {"fromImage": fromImage, "tag": tag} + return utils.dispatch_post_request(url=url_, query_params=query, + host=host) + + def push(self, _id): + abort(404) + + def delete(self, id_, host=None): + url_ = self.url.make_url(id_=id_) + return utils.dispatch_delete_request(url=url_, host=host) + + def tag(self, _id=None, host=None, **kwargs): + url_ = self.url.make_url(url_='tag', id_=id_) + return utils.dispatch_post_request(url=url_, query_params=kwargs, + host=host) diff --git a/dockyard/common/information/__init__.py b/dockyard/common/information/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/information/information.py b/dockyard/common/information/information.py new file mode 100644 index 0000000..fb25b1a --- /dev/null +++ b/dockyard/common/information/information.py @@ -0,0 +1,24 @@ +from dockyard.common import link +from dockyard.common import utils, url + + +class Information(object): + base_url = '/info' + + def __init__(self): + self.url = url.URL(self.base_url) + + def info(self): + url_ = self.url.make_url() + return utils.dispatch_get_request(url_) + + +class Version(object): + base_url = '/version' + + def __init__(self): + self.url = url.URL(self.base_url) + + def version(self): + url_ = self.url.make_url() + return utils.dispatch_get_request(url_) diff --git a/dockyard/common/link.py b/dockyard/common/link.py new file mode 100644 index 0000000..7d1ac04 --- /dev/null +++ b/dockyard/common/link.py @@ -0,0 +1,19 @@ +def make_url(host='127.0.0.1', port=None, protocol='http', url=None): + head = (("%s://%s") % (protocol, host)) + if port: + head = (("%s:%s") % (head, port)) + + if url: + url = (('%s%s') % (head, url)) + else: + url = head + + return url + + +def make_query_url(kwargs): + url = '' + for key, value in kwargs.iteritems(): + url += (('%s=%s&') % (key, value)) + url = url[:-1] + return url diff --git a/dockyard/common/membership/__init__.py b/dockyard/common/membership/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/membership/base.py b/dockyard/common/membership/base.py new file mode 100644 index 0000000..a0aa95d --- /dev/null +++ b/dockyard/common/membership/base.py @@ -0,0 +1,9 @@ +import abc + + +class Membership(object): + @abc.abstractmethod + def get_all_host(self): + """This method returns all the hosts available in the + data center. + """ diff --git a/dockyard/common/membership/consul/__init__.py b/dockyard/common/membership/consul/__init__.py new file mode 100644 index 0000000..041b5b8 --- /dev/null +++ b/dockyard/common/membership/consul/__init__.py @@ -0,0 +1,32 @@ +from oslo_config import cfg + +CONSUL_SERVICE_OPT = [ + cfg.StrOpt('service_name', + default='dockyard', + help='Name of this service used by the consul') +] + +CONF = cfg.CONF +opt_group = cfg.OptGroup(name='consul', + title='Group for the consul parameters') +CONF.register_group(opt_group) +CONF.register_opts(CONSUL_SERVICE_OPT, opt_group) + + +DOCKER_SERVICE_OPT = [ + cfg.IPOpt('docker_host', + default='0.0.0.0', + help='IP address to which docker is binded'), + cfg.IntOpt('docker_port', + default='2375', + help='PORT on which docker is listening'), + cfg.StrOpt('docker_name', + default='docker', + help='Name of the service under which docker is registered') +] + +CONF = cfg.CONF +opt_group = cfg.OptGroup(name='docker', + title='Group for the docker server parameters') +CONF.register_group(opt_group) +CONF.register_opts(DOCKER_SERVICE_OPT, opt_group) diff --git a/dockyard/common/membership/consul/consul_driver.py b/dockyard/common/membership/consul/consul_driver.py new file mode 100644 index 0000000..1279b4c --- /dev/null +++ b/dockyard/common/membership/consul/consul_driver.py @@ -0,0 +1,138 @@ +import consul +import netifaces +from oslo_config import cfg +from oslo_log import log as logging + +from dockyard.common.membership.base import Membership +from dockyard.common import exception +from dockyard.common.membership.consul import utils as consul_utils + +CONF = cfg.CONF + +LOG = logging.getLogger(__name__) + + +def get_localhost_ip(): + """This method resturns localhost ip. + """ + ifs = netifaces.interfaces() + for i in ifs: + try: + addr = netifaces.ifaddresses(i)[netifaces.AF_INET][0]['addr'] + except KeyError: + pass + + if addr == '127.0.0.1': + continue + + yield addr + +class ConsulHealthCheck(object): + def __init__(self): + self.healthy = consul.Consul().health + + def get_healthy_nodes(self, service="dockyard"): + """Get healthy nodes in the cluster. + """ + services = self.healthy.service(service=service, passing=True) + return consul_utils.get_formatted_hosts(services) + + +class Consul(Membership): + def __init__(self): + self.consul = consul.Consul() + self.healthy_services = ConsulHealthCheck() + + def _register_service(self, name, host, port, tags=None, url=''): + if not name: + message = ('Service name to use for registering') + LOG.exception("Cannot continue, Incomplete info: %s" % message) + raise exception.IncompleteInfo(message) + + if not port: + message = ('Port number used by the services to listen') + LOG.exception("Cannot continue, Incomplete info: %s" % message) + raise exception.Incompleteinfo(message) + + if not host: + for ip in get_localhost_ip(): + host = ip + break + + http = ("http://%s:%d/%s" % (host, port, url)) + self.consul.agent.service.register(name=name, + address=host, + port = port, + tags = tags, + http = http, + interval=15) + + def _register_dockyard(self): + ip = CONF['default']['host'] + + if ip == '0.0.0.0': + ip = None + + port = CONF['default']['port'] + tags = ['master'] + name = CONF['consul']['service_name'] + + self._register_service(name, ip, port, tags) + + def _register_docker(self): + ip = CONF['docker']['docker_host'] + if ip == '0.0.0.0': + ip = None + + port = CONF['docker']['docker_port'] + name = CONF['docker']['docker_name'] + + self._register_service(name, ip, port, url='images/json') + + + def register(self): + """ This method registers dockyard service information and + docker server running on this machine. + """ + self._register_dockyard() + self._register_docker() + + def _make_dict(self, service): + """ This method takes all the information returned by the consul + and returns ip address and port of the service. + """ + + if service['ServiceAddress']: + host = service['ServiceAddress'] + else: + host = service['Address'] + + + info = { + 'host': host, + 'port': service['ServicePort'] + } + + return info; + + def _get_services(self, services): + """ This method returns all the registered services. + """ + + services_info = [] + + for service in services[1]: + services_info.append(self._make_dict(service)) + + return services_info + + def get_all_hosts(self, tag='agent'): + """Returns all the members current agent sees. + """ + # services = self.consul.catalog.service('docker') + services = self.healthy_services.get_healthy_nodes() + if not services: + message = "No services are registered to the consul" + raise exception.NoValidHostFound(message) + + return services diff --git a/dockyard/common/membership/consul/utils.py b/dockyard/common/membership/consul/utils.py new file mode 100644 index 0000000..6ee7974 --- /dev/null +++ b/dockyard/common/membership/consul/utils.py @@ -0,0 +1,14 @@ +# This module contains utility required by consul driver only. + +def get_formatted_hosts(services): + """This method is responsible for formatting output as per + need of the dockyard. + """ + healthy_services = [] + for service in services[1]: + service_info = dict() + service_info["host"] = str(service["Service"]["Address"]) + service_info["port"] = service["Service"]["Port"] + healthy_services.append(service_info) + + return healthy_services diff --git a/dockyard/common/network/__init__.py b/dockyard/common/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/network/network.py b/dockyard/common/network/network.py new file mode 100644 index 0000000..2139bd2 --- /dev/null +++ b/dockyard/common/network/network.py @@ -0,0 +1,59 @@ +import json +from pecan import request + +from dockyard.common import url, utils + + +class DockerNetwork(object): + base_url = '/networks' + + def __init__(self): + pass + + def list(self, name_or_id=None, host=None): + url_ = self.url.make_url(id_=name_or_id) + return utils.dispatch_get_request(url=url_, host=host) + + def connect(self, id_, host=None, **kwargs): + body = request.body + url_ = self.url.make_url(url_='connect', id_=id_) + return utils.dispatch_post_request(url=url_, body=body, host=host) + + def disconnect(self, id_, host=None, **kwargs): + url_ = self.url.make_url(url_='disconnect', id_=id_) + body = request.body + return utils.dispatch_post_request(url=url_, body=body, host=host) + + def create(self, host=None, **kwargs): + url_ = self.url.make_url(url_='create') + body = request.body + return utils.dispatch_post_request(url=url_, body=body, host=host) + + def delete(self, id_, host=None): + url_ = self.url.make_url(id_=id_) + return utils.dispatch_delete_request(url=url_, host=host) + + +class DockyardNetwork(object): + dockyard_base_url = '/dockyard' + + def __init__(self): + pass + + def _get_localhost(self): + return utils.get_localhost() + + def attach_floatingip(self, id_, data): + """This method attaches floating ip to the containers. + """ + url_ = self.url.make_dockyard_url(id_=id_, url_='floatingip') + body = request.body + return utils.dispatch_post_request(url=url_, body=body, + host=self._get_localhost()) + + +class Network(DockyardNetwork, DockerNetwork): + def __init__(self): + super(DockerNetwork, self).__init__() + super(DockyardNetwork, self).__init__() + self.url = url.URL(self.base_url, self.dockyard_base_url) diff --git a/dockyard/common/stores/__init__.py b/dockyard/common/stores/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/stores/consul/__init__.py b/dockyard/common/stores/consul/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/stores/consul/consul_client.py b/dockyard/common/stores/consul/consul_client.py new file mode 100644 index 0000000..6f9e309 --- /dev/null +++ b/dockyard/common/stores/consul/consul_client.py @@ -0,0 +1,11 @@ +import consul + +class ConsulKV(object): + def __init__(self): + self.consul = consul.Consul() + + def get(self, key): + return self.consul.kv.get(key) + + def put(self, key, value): + return self.consul.kv.put(key, value) diff --git a/dockyard/common/url.py b/dockyard/common/url.py new file mode 100644 index 0000000..be3a504 --- /dev/null +++ b/dockyard/common/url.py @@ -0,0 +1,55 @@ +class URL(object): + def __init__(self, base_url, dockyard_base_url=None): + self.base_url = base_url + if dockyard_base_url: + self.dockyard_base_url = dockyard_base_url + + def _add_url_id(self, url=None, sub_url=None, id_=None): + """This method takes base_url and url and ids and combine them + to make a base url. + + It creates a URL of the format /command/id. + """ + if not url: + url = "" + + if id_: + url = (("%s/%s") % (url, id_)) + + if sub_url: + url = (("%s/%s") % (url, sub_url)) + + return url + + def make_url(self, url_=None, id_=None): + """It takes id, url as input and makes complete url, first part + of url is fetched from object itself. + """ + url = self._add_url_id(self.base_url, sub_url=url_, id_=id_) + return url + + def _add_dockyard_url(self, url=None, sub_url=None, id_=None): + """This method takes base_url and url and ids and combine them + to make a base url. + + It creates a URL of the format /command/id. + """ + if not url: + url = "" + + if sub_url: + url = (("%s/%s") % (url, sub_url)) + + if id_: + url = (("%s/%s") % (url, id_)) + + return url + + + def make_dockyard_url(self, url_=None, id_=None): + """This method makes url for the API specifics to dockyard only. + This method takes an extra argument of creating dockayrd_base + which is corresponding to the dockayrd abse URL. + """ + url = (("%s%s") % (self.dockyard_base_url, self.base_url)) + return self._add_dockyard_url(url=url, sub_url=url_, id_=id_) diff --git a/dockyard/common/utils.py b/dockyard/common/utils.py new file mode 100644 index 0000000..e8f6729 --- /dev/null +++ b/dockyard/common/utils.py @@ -0,0 +1,215 @@ +import importlib +import netifaces +from oslo_config import cfg +from oslo_log import log as logging +from pecan import request as rcvd_req + +from base import DockyardURL +from urllib3 import PoolManager + +from dockyard.common import link + +SCHEDULER_OPT = [ + cfg.StrOpt('scheduler', + default='round_robin.RoundRobinScheduler', + help='Scheduler for the dockyard.'), + cfg.StrOpt('agent', + default='master', + help='Tags to be used.') +] + +CONF = cfg.CONF +CONF.register_opts(SCHEDULER_OPT, group='default') + +# Fetch scheduler defined in the configuration file and load it. +scheduler_info = CONF.default.scheduler +# May be this path can be specified in the configuration file. +scheduler_loc = 'dockyard.common.container.scheduler' +scheduler_info = (('%s.%s') % (scheduler_loc, scheduler_info)) +module_name, class_name = scheduler_info.rsplit(".", 1) +class_ = getattr(importlib.import_module(module_name), class_name) +scheduler = class_() + + +MEMBERSHIP_OPT = [ + cfg.StrOpt('membership', + default='consul.consul_driver.Consul', + help='Scheduler for the dockyard.'), +] + +CONF.register_opts(MEMBERSHIP_OPT, group='default') + +# Fetch scheduler defined in the configuration file and load it. +membership_info = CONF.default.membership + +# May be this path can be specified in the configuration file. +membership_loc = 'dockyard.common.membership' +membership_info = (('%s.%s') % (membership_loc, membership_info)) +module_name, class_name = membership_info.rsplit(".", 1) +class_ = getattr(importlib.import_module(module_name), class_name) +membership = class_() +membership.register() + +request = DockyardURL() + +def get_config(group, option): + CONF = cfg.CONF + return CONF.group.option + + +def get_host(): + """This method returns host, for serving the request. + + If this instance of the process is api server then scheduler + will be used for scheduling host. + + If this instance of the process is engine then it should direct + to docker procss running on local machine and does preprocessing. + """ + host = '' + try: + rcvd_req.headers['Request-Status'] + except KeyError: + hosts = membership.get_all_hosts() + host = scheduler.get_host(hosts=hosts) + + if (not host) or is_localhost(host=host): + host = { + 'host': CONF['docker']['docker_host'], + 'port': CONF['docker']['docker_port'] + } + + return host + + +def get_localhost_ip(): + """This method resturns localhost ip. + """ + ifs = netifaces.interfaces() + for i in ifs: + try: + addr = netifaces.ifaddresses(i)[netifaces.AF_INET][0]['addr'] + except KeyError: + pass + + if addr == '127.0.0.1': + continue + + yield addr + + +def get_localhost(): + d = dict() + + if CONF.default.host == '0.0.0.0': + d['host'] = '127.0.0.1' + else: + d['host'] = CONF.default.host + + d['port'] = CONF.default.port + return d + + +def is_localhost(host): + """This method returns whether specified host is local machine's IP + or not. + """ + localhost_ips = get_localhost_ip() + for ip in localhost_ips: + if ip == host['host']: + return True + + return False + + +def get_link(url, host=None, protocol='http'): + headers = dict() + + if not host: + host = get_host() + headers['Request-Status'] = 'Scheduled' + + url = link.make_url(host=host['host'], port=host['port'], url=url) + return (url, headers) + +def merge_dict(x=dict(), y=dict()): + """Merge two dictionaries. + """ + z = x.copy() + z.update(y) + return z + +def prepare_logging(argv=None): + """ + log file can be specified as a command line argument + with key = log-file + """ + if argv is None: + argv = [] + + logging.register_options(CONF) + CONF(argv[1:], project='dockyard') + logging.setup(CONF, 'dockyard') + +def dispatch_get_request(url, headers=dict(), protocol='http', + query_params=None, host=None): + + (ln, hdr) = get_link(host=host, url=url, protocol=protocol) + + if query_params: + query = link.make_query_url(query_params) + ln = (('%s?%s') % (ln, query)) + + headers = merge_dict(headers, hdr) + return request.send(method='GET', url=ln, headers=headers) + + +def dispatch_post_request(url, host=None, protocol='http', + body=None, query_params=None): + + (ln, hdr) = get_link(host=host, url=url, protocol=protocol) + + if query_params: + query = link.make_query_url(query_params) + ln = (('%s?%s') % (ln, query)) + + headers = merge_dict(headers, hdr) + return dispatch_post_req(url=ln, post_params=query_params, + body=body, headers=headers) + + +def dispatch_put_request(url, protocol='http', body=None, + host=None, query_params=None): + + (ln, hdr) = get_link(host=host, url=url, protocol=protocol) + + if query_params: + query = link.make_query_url(query_params) + ln = (('%s?%s') % (ln, query)) + + headers = merge_dict(headers, hdr) + return dispatch_put_req(url=ln, post_params=query_params, + body=body, headers=headers) + + +def dispatch_delete_request(url, headers=dict(), protocol='http', + query_params=None, host=None): + (ln, hdr) = get_link(url=url, protocol=protocol, host=None) + headers = merge_dict(headers, hdr) + return request.send(method='DELETE', url=ln, headers=headers) + + +def dispatch_post_req(url, headers=dict(), body=None, post_params=None): + if not headers: + hdr = {'Content-Type': 'application/json'} + headers = merge_dict(headers, hdr) + + return request.send(method='POST', url=url, headers=headers, body=body) + + +def dispatch_put_req(url, headers=dict(), body=None, post_params=None): + if not headers: + headers = {'Content-Type': 'application/x-tar'} + headers = merge_dict(headers, hdr) + + return request.send(method='PUT', url=url, headers=headers, body=body) diff --git a/dockyard/common/volume/__init__.py b/dockyard/common/volume/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/common/volume/volume.py b/dockyard/common/volume/volume.py new file mode 100644 index 0000000..6820e85 --- /dev/null +++ b/dockyard/common/volume/volume.py @@ -0,0 +1,22 @@ +import json +from dockyard.common import link +from dockyard.common import utils, url + + +class Volume(object): + base_url = '/volumes' + + def __init__(self): + self.url = url.URL(self.base_url) + + def list(self, name=None, host=None): + url_ = self.url.make_url(id_=name) + return utils.dispatch_get_request(url_, host=host) + + def delete(self, name, host=None): + url_ = self.url.make_url(id_=name) + return utils.dispatch_delete_request(url_, host=host) + + def create(self, data, host=None): + url_ = self.url.make_url(url_='create') + return utils.dispatch_post_request(url=url_, body=data, host=None) diff --git a/dockyard/controllers/__init__.py b/dockyard/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/controllers/root.py b/dockyard/controllers/root.py new file mode 100644 index 0000000..cc21fd0 --- /dev/null +++ b/dockyard/controllers/root.py @@ -0,0 +1,41 @@ +from oslo_log import log as logging + +from pecan import expose, rest, request + +from dockyard.controllers import v1 + +LOG = logging.getLogger(__name__) + +class RootController(rest.RestController): + + _version = ['v1'] + # List of the allowed versions of the API. + + _default_version = 'v1' + # Default value of the API version. + v1 = v1.Controller() + + @expose() + def index(self): + return "OK\n" + + @expose() + def _route(self, args): + """Override default routing. + + It redirect to the default value of the dockyard API, if version + of the API is not specified or wrongly specified + """ + + if args[0] and args[0] not in self._version: + args = [self._default_version] + args + + if request.body: + msg = ("Processing request: url: %(url)s, " + "method: %(method)s, " + "body: %(body)s" % + {'url': request.url, + 'method': request.method, + 'body': request.body}) + LOG.debug(msg) + return super(RootController, self)._route(args) diff --git a/dockyard/controllers/v1/__init__.py b/dockyard/controllers/v1/__init__.py new file mode 100644 index 0000000..5738274 --- /dev/null +++ b/dockyard/controllers/v1/__init__.py @@ -0,0 +1,43 @@ +from pecan import rest +from pecan import expose +from oslo_config import cfg + +from dockyard.controllers.v1 import information +from dockyard.controllers.v1 import container +from dockyard.controllers.v1 import image +from dockyard.controllers.v1 import network +from dockyard.controllers.v1 import volume +from dockyard.controllers.v1 import cluster +from dockyard.engine.controllers.dockyard_controllers import ( + DockyardEngineController) + + +SERVER_OPT = [ + cfg.BoolOpt('api', + default='True', + help=('True for api server, False ' + 'for disabling api server')), + cfg.BoolOpt('engine', + default='True', + help=('True for engine server, ' + 'False for disabling engine server.')) + ] + +CONF = cfg.CONF +CONF.register_opts(SERVER_OPT, group='default') + + +class Controller(rest.RestController): + """Controller for both the api server and engine. + """ + if CONF.default.api: + info = information.Information() + version = information.Version() + containers = container.ContainerController() + images = image.ImageController() + networks = network.NetworkController() + volumes = volume.VolumeController() + clusters = cluster.ClusterController() + + if CONF.default.engine: + dockyard = DockyardEngineController() diff --git a/dockyard/controllers/v1/cluster.py b/dockyard/controllers/v1/cluster.py new file mode 100644 index 0000000..677d5f3 --- /dev/null +++ b/dockyard/controllers/v1/cluster.py @@ -0,0 +1,49 @@ +from pecan import expose, abort +from oslo_config import cfg + +from dockyard.common.cluster import cluster + + +class Cluster(object): + def __init__(self): + self.cluster = cluster.Cluster() + + @expose(generic=True) + def register(self, *args): + abort(404) + + @register.when(method="PUT") + def register_POST(self, cluster_id, host_ip, port): + return self.cluster.register(cluster_id, host_ip, port) + + @expose(generic=True) + def unregister(self, *args): + abort(404) + + @unregister.when(method="DELETE") + def unregister_DELETE(self, cluster_id, host_ip): + return self.cluster.unregister(cluster_id, host_ip) + + +class ClusterController(object): + def __init__(self): + HOST_SERVICE_OPT = [ + cfg.ListOpt('hosts', + default='127.0.0.1:3333', + help='Listening address of docker service'), + ] + + CONF = cfg.CONF + opt_group = cfg.OptGroup(name='membership', title=("Group \ + for membership of docker services")) + CONF.register_group(opt_group) + CONF.register_opts(HOST_SERVICE_OPT, opt_group) + + @expose() + def _lookup(self, op, cluster_id, host_ip, port=None): + new_url = [op, cluster_id, host_ip] + + if port: + new_url.append(port) + + return Cluster(), tuple(new_url) diff --git a/dockyard/controllers/v1/container.py b/dockyard/controllers/v1/container.py new file mode 100644 index 0000000..0eaa9e6 --- /dev/null +++ b/dockyard/controllers/v1/container.py @@ -0,0 +1,107 @@ +from pecan import expose, abort, request +from pecan.rest import RestController + +from dockyard.common.container import container + + +class ContainerController(RestController): + def __init__(self): + self.container = container.Container() + + def _call_operation(self, name_or_id, operation, **kwargs): + try: + return getattr(self, operation)(name_or_id) + except AttributeError: + abort(404) + + @expose() + def get(self, name_or_id, operation=None, **kwargs): + return self._call_operation(name_or_id, operation, **kwargs) + + @expose() + def post(self, name_or_id, operation, **kwargs): + return self._call_operation(name_or_id, operation, **kwargs) + + @expose() + def put(self, name_or_id, operation, **kwargs): + return self._call_operation(name_or_id, operation, **kwargs) + + @expose() + def delete(self, _id, **kwargs): + return self.container.delete(_id, **kwargs) + + def resize(self, _id, **kwargs): + return self.container.resize(_id, **kwargs) + + def changes(self, _id): + return self.container.changes(_id) + + def export(self, _id): + return self.container.export(_id) + + def json(self, name_id=None): + query_params = request.query_string + return self.container.list(name_id, query_params=query_params) + + def stats(self, _id): + return self.container.stats(_id) + + def archive(self, _id): + query_params = request.query_string + return self.container.archive(_id, query_params) + + def upload(self, _id, **kwargs): + body = request.body + query_params = request.query_string + return self.container.upload(_id, body=body, query_params=query_params) + + def copy_POST(self, _id): + return self.container.copy(_id) + + def logs(self, _id): + query_params = request.query_string + return self.container.logs(_id, query_params) + + def start(self, _id): + query_params = request.query_string + return self.container.start(_id, query_params=query_params) + + def kill(self, _id): + query_params = request.query_string + return self.container.kill(_id, query_params=query_params) + + def restart(self, _id): + return self.container.restart(_id) + + def stop(self, _id): + query_params = request.query_string + return self.container.stop(_id, query_params=query_params) + + def exec_(self, _id): + return self.container.exe(_id) + + def attach(self, _id): + query_params = request.query_string + return self.container.attach(_id, query_params=query_params) + + def rename(self, _id, **kwargs): + return self.container.rename(_id, **kwargs) + + def top(self, _id): + query_params = request.query_string + return self.container.top(_id, query_params=query_params) + + def update(self, _id, **kwargs): + return self.container.update(_id, kwargs) + + def pause(self, _id): + return self.container.pause(_id) + + def unpause(self, _id): + return self.container.unpause(_id) + + def wait(self, _id): + return self.container.wait(_id) + + def create(self, **args): + return self.container.create(args) diff --git a/dockyard/controllers/v1/image.py b/dockyard/controllers/v1/image.py new file mode 100644 index 0000000..f9ff25f --- /dev/null +++ b/dockyard/controllers/v1/image.py @@ -0,0 +1,70 @@ +from pecan import expose, route +from pecan.rest import RestController + +from dockyard.common.image import image + + +class ImageController(RestController): + def __init__(self): + self.image = image.Image() + + @expose() + def get_one(self, name_or_id, operation): + if not operation: + operation = name_or_id + name_or_id = None + + return getattr(self, operation)(name_or_id) + + @expose() + # This method is not working, it has to be corrected. + def get(self, operation): + return getattr(self, operation)() + + def search(self, term): + return self.search(term=name_or_id) + + def history(self, name_or_id): + return self.history(name_or_id) + + def json(self, name_or_id=None): + return self.list_(_id=name_or_id) + + def push(self, _id): + return self.image.push(_id) + + @expose() + def post(self, name=None, operation=None): + if not operation: + operation = name_or_id + name_or_id = None + + return getattr(self, operation)(name_or_id) + + # Test this method + def create(self, fromImage, tag='latest'): + return self.image.create(fromImage, tag) + + def tag(self, name, **kwargs): + return self.image.tag(id_or_name, kwargs) + + # This method has to be put in the misc section, as per API + # it does not seems to belong to images + def build(self, **kwargs): + return self.image.build(kwargs) + + def push(self): + return self.push(name) + + @expose() + def delete(self, name): + return self.image.delete(name) + + def list_(self, _id=None): + return self.image.list(_id) + + def search(self, term=None): + return self.image.search(term) + + def history(self, _id=None): + return self.image.history(_id) diff --git a/dockyard/controllers/v1/information.py b/dockyard/controllers/v1/information.py new file mode 100644 index 0000000..efc239f --- /dev/null +++ b/dockyard/controllers/v1/information.py @@ -0,0 +1,22 @@ +from pecan import expose +from pecan.rest import RestController + +from dockyard.common.information import information + + +class Information(RestController): + def __init__(self): + self.information = information.Information() + + @expose() + def get_all(self): + return self.information.info() + + +class Version(RestController): + def __init__(self): + self.information = information.Version() + + @expose() + def get_all(self): + return self.information.version() diff --git a/dockyard/controllers/v1/network.py b/dockyard/controllers/v1/network.py new file mode 100644 index 0000000..ba700a5 --- /dev/null +++ b/dockyard/controllers/v1/network.py @@ -0,0 +1,41 @@ +from pecan import expose, abort +from pecan.rest import RestController + +from dockyard.common.network import network + + +class NetworkController(RestController): + def __init__(self): + self.network = network.Network() + + @expose() + def post(self, _id, operation=None, **kwargs): + if not operation: + operation = _id + _id = None + + return getattr(self, operation)(_id=_id, **kwargs) + + def create(self, _id=None, **kwargs): + return self.network.create(**kwargs) + + def connect(self, _id=None, **kwargs): + return self.network.connect(_id, **kwargs) + + @expose() + def get_one(self, name_or_id): + return self.network.list(name_or_id) + + @expose() + def get(self): + return self.network.list() + + @expose() + def delete(self, name_or_id): + return self.network.delete(name_or_id) + + def disconnect(self, _id, **kwargs): + return self.network.disconnect(_id, **kwargs) + + def floatingip(self, _id, **kwargs): + return self.network.attach_floatingip(_id, kwargs) diff --git a/dockyard/controllers/v1/volume.py b/dockyard/controllers/v1/volume.py new file mode 100644 index 0000000..40ee6cd --- /dev/null +++ b/dockyard/controllers/v1/volume.py @@ -0,0 +1,27 @@ +from pecan import expose, request +from pecan.rest import RestController + +from dockyard.common.volume import volume + +class VolumeController(RestController): + """This class exposes all the API's related with volume. + """ + def __init__(self): + self.volume = volume.Volume() + + @expose() + def get_one(self, name=None): + return self.volume.list(name=name) + + @expose() + def get(self): + return self.volume.list() + + @expose() + def delete(self, name): + return self.volume.delete(name=name) + + @expose() + def post(self, operation=None): + body = request.body + return self.volume.create(data=body) diff --git a/dockyard/engine/__init__.py b/dockyard/engine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine/common/__init__.py b/dockyard/engine/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine/common/containers/__init__.py b/dockyard/engine/common/containers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine/common/exceptions.py b/dockyard/engine/common/exceptions.py new file mode 100644 index 0000000..0acfa23 --- /dev/null +++ b/dockyard/engine/common/exceptions.py @@ -0,0 +1,2 @@ +class InterfaceNotFound(object): + msg = ("Interface not found.") diff --git a/dockyard/engine/common/network/__init__.py b/dockyard/engine/common/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine/common/network/drivers/__init__.py b/dockyard/engine/common/network/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine/common/network/drivers/bridges/__init__.py b/dockyard/engine/common/network/drivers/bridges/__init__.py new file mode 100644 index 0000000..113373f --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/__init__.py @@ -0,0 +1,9 @@ +from oslo_config import cfg + +HOST_SERVICE_OPT = [ + cfg.StrOpt('bridge', + default='br100', + help='Bridge for the external connectivity network'), +] + +cfg.CONF.register_opts(HOST_SERVICE_OPT, 'network') diff --git a/dockyard/engine/common/network/drivers/bridges/base.py b/dockyard/engine/common/network/drivers/bridges/base.py new file mode 100644 index 0000000..e48f009 --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/base.py @@ -0,0 +1,374 @@ +# This module does the common tasks required by driver, which are based +# on the bridges concepts. It does following tasks +# 1) Create Virtual Interface # 2) Moving Virtual Interface into docker namespace # 3) Assign IP address to the interface # 4) Brings up network interface + +import abc + +from oslo_log import log as logging +from network_driver_exceptions import ( + AlreadyInNamespace, FailedToMoveInterface, + InterfaceNotFound, + InvalidState, + NamespaceNotFound, + UnableToAssignIP, + UnableToAddRoutes, + UnableToChangeState) + +from pyroute2 import IPDB, NetNS +from namespace import DockyardNamespace + + +LOG = logging.getLogger(__name__) +# make sure this files interfaces are updated as per +# changes. +class BridgeManager(object): + def __init__(self): + pass + + @abc.abstractmethod + def create_link_pair(self, kind, peer=None): + pass + + @abc.abstractmethod + def attach_if(self, master, bridge): + pass + + @abc.abstractmethod + def addr(self, address, mask, broadcast, net_ns_fd): + pass + + @abc.abstractmethod + def move_to_namespace(self, ifname, net_ns_fd): + pass + + @abc.abstractmethod + def change_state(self, ifname, state): + pass + + @abc.abstractmethod + def get_index(self, ifname): + pass + + +class IPDBManager(object): + def __init__(self): + pass + + def open_ipdb(self, net_ns_fd=None): + self.ns = None + + if net_ns_fd: + self.ns = NetNS(net_ns_fd) + ipdb = IPDB(nl=self.ns) + else: + ipdb = IPDB() + + return ipdb + + + def close_ipdb(self, ipdb): + if self.ns: + self.ns.close() + + ipdb.commit() + ipdb.release() + + +class Addr(object): + def __init__(self): + self.ipdb_manager = IPDBManager() + + def add(self, ifname=None, address=None, + mask=None, broadcast=None, net_ns_fd=None): + """Add ip address to the interface in namespace or outside + the name space. + """ + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd=net_ns_fd) + + if address: + address = ("%s/%d" % (address, mask)) + + with ipdb.interfaces[ifname] as interface: + if address: + interface.add_ip(address) + + self.ipdb_manager.close_ipdb(ipdb) + + def routes(self, dst, gateway, oif, net_ns_fd=None, **kwargs): + """Add routes to the namespace. + :dst: destination for which routes are being added. + :gateway: Gateway to be set. + :net_ns_fd: Network namespace file descriptor. + :kwargs: In case of advanced networking, additional parameters + might be provided through this option. + """ + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd=net_ns_fd) + t = {} + t['dst'] = dst + t['gateway'] = gateway + t['oif'] = oif + + specs = t.copy() + specs.update(kwargs) + + with ipdb.routes[dst] as route: + for key, value in specs.iteritems(): + if value == dst: + continue + route[key] = value + + self.ipdb_manager.close_ipdb(ipdb) + + +class Link(object): + allowed_states = ['up', 'down'] + + def __init__(self): + self.ipdb_manager = IPDBManager() + + def create(self, ifname, peer, kind='veth', net_ns_fd=None): + """Create link. + """ + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd) + ipdb.create(ifname=ifname, kind=kind, peer=peer) + self.ipdb_manager.close_ipdb(ipdb) + + def move_to_namespace(self, ifname, net_ns_fd): + """Move an interface to the namespace. + """ + ipdb = self.ipdb_manager.open_ipdb() + + with ipdb.interfaces[ifname] as interface: + interface.net_ns_fd = net_ns_fd + + self.ipdb_manager.close_ipdb(ipdb) + + def set_state(self, ifname, net_ns_fd=None, state=None): + """Set state of the interface up/down in the namespace. + """ + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd) + + with ipdb.interfaces[ifname] as interface: + getattr(interface, state)() + + self.ipdb_manager.close_ipdb(ipdb) + + def get_ifs(self, net_ns_fd=None): + """Look up all the interfaces. + """ + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd) + ifs = [x for x in ipdb.interfaces if isinstance(x, str)] + self.ipdb_manager.close_ipdb(ipdb) + return ifs + + def get_if(self, name, net_ns_fd=None): + """Look up all the interfaces. + """ + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd) + if_info = ipdb.interfaces[name] + self.ipdb_manager.close_ipdb(ipdb) + return if_info + + def does_if_exist(self, ifname, net_ns_fd=None): + """This method checks whether a interface is in the + namespace. + + ifname: interface name of the network interface + net_ns_fd: Namespace file descirptor. + + It returns True or False, depending on whether interface + is in namespace or not. + """ + + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd) + try: + ipdb.interfaces[ifname] + except: + return False + else: + self.ipdb_manager.close_ipdb(ipdb) + + return True + + def attach_port(self, ifname, bridge, net_ns_fd=None): + """This method attach interface to the bridge. + :ifname: interface name to attach to the bridge. + :bridge: Name of the bridge to which attach interface. + """ + ipdb = self.ipdb_manager.open_ipdb(net_ns_fd=net_ns_fd) + + with ipdb.interfaces[bridge] as br: + br.add_port(ipdb.interfaces[ifname]) + + self.ipdb_manager.close_ipdb(ipdb) + + +class IPManager(object): + def __init__(self): + """Manages IP manager. + """ + self.netns = DockyardNamespace() + self.addr = Addr() + self.link = Link() + + def _does_ns_exist(self, psid): + return self.netns.does_exist(psid) + + def _check_and_attach(self, psid): + if not self._does_ns_exist(psid): + try: + self.netns.attach_namespace(psid) + except Exception as e: + msg = ("%s Namespace does not exist. ERROR: %s" % (psid, e)) + LOG.exception(msg) + raise NamespaceNotFound(msg) + + return psid + + def assign_ip(self, ifname, address, mask, broadcast=None, psid=None): + """Assign ip address. + :ifname: Assign ip address to this interface. + :address: Assign this ip address. + :mask: for the network + :broadcast: broadcast address for the network. + :net_ns_fd: network file descriptor or namespace. + + :raises NamespaceNotFound, UnableToAssignIP + """ + netns_name = None + + if psid: + psid = self._check_and_attach(psid) + netns_name = self.netns.get_netns_name(psid=psid) + + try: + self.addr.add(ifname, address, mask, + broadcast, net_ns_fd=netns_name) + except Exception as e: + msg = ("Unable to assign ip %s to %s interface in psid namespace." + "ERROR: %s" % (address, ifname, psid, e)) + + LOG.exception(msg) + + raise UnableToAssignIP(msg) + + def add_routes(self, dst, gateway, oif_name, psid=None, **kwargs): + """Add routes to the namespace. + :dst: destination for which routes are being added. + :gateway: Gateway to be set. + :net_ns_fd: Network namespace file descriptor. + :kwargs: In case of advanced networking, additional parameters + might be provided through this option. + """ + netns_name = None + + if psid: + psid = self._check_and_attach(psid) + netns_name = self.netns.get_netns_name(psid=psid) + + if self.link.does_if_exist(oif_name, net_ns_fd=netns_name): + oif_idx = self.link.get_if(oif_name, net_ns_fd=netns_name) + oif_idx = oif_idx['index'] + else: + msg = ("%s interface for setting default route in %s namespace " + "is not found" % (oif_name, netns_name)) + + raise InterfaceNotFound(msg) + + try: + if netns_name: + self.addr.routes(oif=oif_idx, dst=dst, gateway=gateway, + net_ns_fd=netns_name, **kwargs) + else: + self.addr.routes(oif=oif_idx, dst=dst, + gateway=gateway, **kwargs) + except Exception as e: + msg = ("Unable to add gateway %s for destination %s in namespace " + "%s for interface %d. ERROR: %s" % (gateway, dst, + netns_name, oif_idx, e)) + LOG.exception(msg) + raise UnableToAddRoutes(msg) + + +class InterfaceManager(object): + def __init__(self): + self.link = Link() + self.netns = DockyardNamespace() + + def _does_ns_exist(self, psid): + return self.netns.does_exist(psid) + + def _does_if_exist(self, ifname, psid=None): + """Checks whether interface exist or not in a namespace. + :ifname: Name of the interface. + :psid: process id of the container. + + :returns True or False based on whether ifname exist or not. + """ + ns = False + + if not psid: + ns = self.link.does_if_exist(ifname) + else: + netns_name = self.netns.get_netns_name(ifname, psid) + ns = self.link.does_if_exist(ifname, net_ns_fd=netns_name) + + return ns + + def move_to_namespace(self, ifname, psid): + """Moves interface to the namspace. + :ifname: Interface name. + :psid: Process id for the docker container. + + :raises NamespaceNotFound, FailedToMoveInterface + """ + + if not self._does_if_exist(ifname): + msg = ("%s interface does not exist" % (ifname)) + raise InterfaceNotFound(msg) + + if not self._does_ns_exist(psid): + try: + self.netns.attach_namespace(psid) + except Exception as e: + msg = ("%s Namespace does not exist. ERROR: %s" % (ifname, e)) + LOG.exception(msg) + raise NamespaceNotFound(msg) + + try: + netns_name = self.netns.get_netns_name(psid) + self.link.move_to_namespace(ifname=ifname, + net_ns_fd=netns_name) + except Exception as e: + msg = ("Failed to move %s interface in %s namespace. ERROR: %s" % ( + ifname, netns_name, e)) + LOG.exception(msg) + raise FailedToMoveInterface(msg) + + def change_state(self, ifname, state='up', psid=None): + """Brings interface ups. + :ifname: Interface name + :state: Expected state of the interface. + :psid: Process id of the docker process. + + :raises InvalidState, UnableToChangeState + """ + if state not in self.link.allowed_states: + msg = ("States has to be among %s but Received %s state" % ( + self.link.allowed_states, state)) + LOG.exception(msg) + raise InvalidState(msg) + + if psid: + netns_name = self.netns.get_netns_name(psid=psid) + else: + netns_name = None + + try: + self.link.set_state(state=state, ifname=ifname, + net_ns_fd=netns_name) + except Exception as e: + msg = ("Unable to change state of %s interface to %s state in " + "%s namespace.ERROR: %s" % (ifname, state, net_ns_fd, e)) + LOG.exception(msg) + raise UnableToChangeState(msg) diff --git a/dockyard/engine/common/network/drivers/bridges/linux.py b/dockyard/engine/common/network/drivers/bridges/linux.py new file mode 100644 index 0000000..f53224c --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/linux.py @@ -0,0 +1,160 @@ +from oslo_log import log as logging +from base import InterfaceManager, IPManager, Link +from namespace import DockyardNamespace + +from network_driver_exceptions import ( + InsufficientInfo, + UnableToAttachPort, + NamespaceNotFound, + UnableToCreateInterface) +from utils import RandomNumber + +LOG = logging.getLogger(__name__) + +class LinuxBridgeManager(object): + MAX_IF_LENGTH = 15 + + def __init__(self): + self.link = Link() + self.if_manager = InterfaceManager() + self.ip_manager = IPManager() + self.namespace = DockyardNamespace() + + def _get_ifname(self, prefix='deth'): + """This method makes name of the interface in the specified format + for our application. + + Name returned by this method should not be more than 15 length + character. + """ + + rand_num = RandomNumber() + id_ = rand_num.get_number(self.MAX_IF_LENGTH - (len(prefix) + 1)) + return ('%s-%s' % (prefix, id_)); + + + def create_link_pair(self, ifname=None, kind='veth', peer=None): + """Creates links, one for namespace and other out of namespace. + + ifname: Name of the interface. In this application, it will + contain id of the container, which will be appended + to the '[kind]-[i/x]' therefor it produce unique interface + name. + kind: It is the veth in our application. + peer: Name of the peer link, In this application link, out + of the namespace contains '[kind]-x-[id]' for external + link and '[kind]-i-[id]' for the namespace link. + """ + + if not ifname: + ext_if = self._get_ifname() + else: + ext_if = ifname + + if not peer: + int_if = self._get_ifname() + else: + int_if = peer + + try: + self.link.create(ifname=ext_if, kind=kind, peer=int_if) + except Exception as e: + msg = ("Unable to create %s, %s interfaces of %s kind. ERROR: %s" + % (ext_if, int_if, kind, e)) + + LOG.exception(msg) + return msg + + return {'ext_if': ext_if, 'int_if': int_if} + + def attach_port(self, ifname, br_name): + """Attach interface to bridges. + """ + try: + self.link.attach_port(ifname=ifname, bridge=br_name) + LOG.info("Attached interface: %s to bridge: %s" % (ifname, br_name)) + except Exception as e: + msg = ("Unable to attach %s interface with %s bridge. ERROR: %s" + % (ifname, br_name, e)) + LOG.exception(msg) + raise UnableToAttachPort(msg) + + def move_to_namespace(self, ifname, psid): + """move an interface to the docker process namespace. + :ifname: Interface name + :psid: Docker processs name. + + :raises InterfaceNotFound, NamespaceNotFound, FailedTOMoveInterface + """ + + self.if_manager.move_to_namespace(ifname, psid) + + def addr(self, ifname, address, mask, broadcast=None, psid=None): + """Assign ip address. + :ifname: Assign ip address to this interface. + :address: Assign this ip address. + :mask: for the network + :broadcast: broadcast address for the network. + :net_ns_fd: network file descriptor or namespace. + + :raises NamespaceNotFound, UnableToAssignIP + """ + self.ip_manager.assign_ip(ifname, address, mask, broadcast, psid) + + def change_state(self, ifname, state='up', psid=None): + """Change state of the interface. + :ifname: Interface name + :state: Expected state of the interface valid values + are up, down. + :psid: process id for the docker container. + + :raises InvalidState, UnableToChangeState + """ + self.if_manager.change_state(ifname=ifname, state=state, psid=psid) + + def add_routes(self, oif_name, dst='default', gateway='0.0.0.0', + psid=None, **kwargs): + """Add routes to the namespace. + :dst: destination for which routes are being added. + :gateway: Gateway to be set. + :net_ns_fd: Network namespace file descriptor. + :kwargs: In case of advanced networking, additional parameters + might be provided through this option. + """ + netns_name = None + + if psid: + netns_name = self.if_manager.netns.get_netns_name(psid=psid) + + self.ip_manager.add_routes(oif_name=oif_name, dst=dst, gateway=gateway, + psid=psid, **kwargs) + + def get_ifs(self, psid=None): + """This method returns all the network interfaces defined in docker + network namespace. + """ + netns_name = None + + if psid: + netns_name = self.if_manager.netns.get_netns_name(psid=psid) + + try: + self._check_and_attach(psid) + except NamespaceNotFound as e: + # 404 into the header, which suggest on client side + # about this issue. + return ("Namespace %d could not be found. ERROR: %s" % (psid, e)) + + return self.link.get_ifs(net_ns_fd=netns_name) + + # This method has been defined in at 3 places, it must be put into + # namespace class so that it can be reused at all the places. + def _check_and_attach(self, psid): + if not self.namespace.does_exist(psid): + try: + self.namespace.attach_namespace(psid) + except Exception as e: + msg = ("%s Namespace does not exist. ERROR: %s" % (psid, e)) + raise NamespaceNotFound(msg) + + return psid diff --git a/dockyard/engine/common/network/drivers/bridges/namespace.py b/dockyard/engine/common/network/drivers/bridges/namespace.py new file mode 100644 index 0000000..1fa4bb5 --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/namespace.py @@ -0,0 +1,78 @@ +# This module is responsible for making namespace descriptor to be +# present in /var/run/netns/. + +from network_driver_exceptions import UnableToAttachNamespace +from symlink import Symlink + +class Namespace(object): + def __init__(self): + pass + +class DockyardNamespace(Namespace): + _DOCKYARD_BASE_NETNS = "/var/run/dockyard/%d/ns/net" + _BASE_NETNS = "/var/run/netns" + + def __init__(self): + self.symlink = Symlink() + + def __attach_namespace(self, src, dst): + """This method attach to existing namespace. + src: It is the location of the file descriptor of the + namespace to attach. + dst: It is the location, where new file descriptor will be + created. + """ + try: + self.symlink.create(src, dst) + except: + msg = ("Unable to attach to docker namespace") + raise UnableToAttachNamespace(msg) + + def _get_src_netns(self, psid): + """This method return the orignial network namespace for + a docker container. + """ + path = ("/proc/%d/ns/net" % (psid)) + return path + + def _get_netns_loc(self, psid): + """Get network namespace location for the dockyard. + """ + return ("%s/dockyard_%d" % (self._BASE_NETNS, psid)) + + def attach_netns_namespace(self, psid): + """This method creates namespace for the pyroute library. + """ + src_netns = self._get_src_netns(psid) + dst_netns = ("%s/%s" % (self._BASE_NETNS, self.get_netns_name(psid))) + self.__attach_namespace(src_netns, dst_netns) + + def attach_dockyard_namespace(self, psid): + """This method creates namespace for the dockyard. + """ + src_netns = self._get_src_netns(psid) + dst_netns = self._get_netns_loc(psid) + self.__attach_namespace(src_netns, dst_netns) + + def attach_namespace(self, psid): + """Attach network namespace for dockyard. + """ + self.attach_netns_namespace(psid) + + def does_exist(self, psid): + """Checks whether network namespace exist or not. + """ + path = self._get_netns_loc(psid) + return self.symlink.is_symlink(path) + + def get_netns_name(self, psid): + """Returns name of the namespace. + """ + netns_name = ("dockyard_%d" % (psid)) + return netns_name + + def cleanup(self, psid): + """cleanup namespace by removing link to it. + """ + netns_loc = self._get_netns_loc(psid) + self.symlink.cleanup(netns_loc) diff --git a/dockyard/engine/common/network/drivers/bridges/network_driver_exceptions.py b/dockyard/engine/common/network/drivers/bridges/network_driver_exceptions.py new file mode 100644 index 0000000..111b555 --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/network_driver_exceptions.py @@ -0,0 +1,46 @@ +# This module holds all the exceptions used by bridge type driver. +class BridgeDriversExceptions(Exception): + pass + +class NamespaceNotFound(BridgeDriversExceptions): + pass + +class UnableToAttachPort(BridgeDriversExceptions): + pass + +class UnableToAddRoutes(BridgeDriversExceptions): + pass + +class UnableToAttachNamespace(BridgeDriversExceptions): + pass + + +class InsufficientInfo(BridgeDriversExceptions): + pass + + +class AlreadyInNamespace(BridgeDriversExceptions): + pass + + +class InterfaceNotFound(BridgeDriversExceptions): + pass + + +class FailedToMoveInterface(BridgeDriversExceptions): + pass + + +class UnableToAssignIP(BridgeDriversExceptions): + pass + + +class UnableToChangeState(BridgeDriversExceptions): + pass + + +class InvalidState(BridgeDriversExceptions): + pass + +class UnableToCreateInterface(BridgeDriversExceptions): + pass diff --git a/dockyard/engine/common/network/drivers/bridges/symlink.py b/dockyard/engine/common/network/drivers/bridges/symlink.py new file mode 100644 index 0000000..789df0c --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/symlink.py @@ -0,0 +1,25 @@ +# This module is responsible for creating links for the required +# namespace. + +import os + + +class Symlink(object): + def __init__(self): + pass + + def create(self, src, dst): + """This method is responsible for creating symbolic links. + """ + os.symlink(src, dst) + + def is_symlink(self, path): + """This method checks whether given path is symlink or not. + """ + return os.path.islink(path) + + def cleanup(self, path): + """Remove the file from a path. + :path: Path of the file to remove. + """ + os.remove(path) diff --git a/dockyard/engine/common/network/drivers/bridges/test_add_port.py b/dockyard/engine/common/network/drivers/bridges/test_add_port.py new file mode 100644 index 0000000..c605b00 --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/test_add_port.py @@ -0,0 +1,3 @@ +from linux import LinuxBridgeManager +obj = LinuxBridgeManager() +obj.attach_if('deth-QXQZrnifAQ', 'br100') diff --git a/dockyard/engine/common/network/drivers/bridges/test_cleanup.py b/dockyard/engine/common/network/drivers/bridges/test_cleanup.py new file mode 100644 index 0000000..02bb23b --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/test_cleanup.py @@ -0,0 +1,6 @@ +from namespace import DockyardNamespace +psid=[2057] +ns = DockyardNamespace() +for pid in psid: + ns.cleanup(pid) + diff --git a/dockyard/engine/common/network/drivers/bridges/test_linux.py b/dockyard/engine/common/network/drivers/bridges/test_linux.py new file mode 100644 index 0000000..704e6be --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/test_linux.py @@ -0,0 +1,13 @@ +from linux import LinuxBridgeManager +#from namespace import DockyardNamspace + +obj = LinuxBridgeManager() +#ns = DockyardNamespace() +psid=8074 +ifs = obj.create_link_pair() +obj.attach_port(ifname=ifs['ext_if'], br_name='br100') +obj.move_to_namespace(ifname=ifs['int_if'], psid=psid) +obj.change_state(ifname=ifs['int_if'], state='up', psid=psid) +obj.addr(ifname=ifs['int_if'], address='192.168.100.7', mask=24, psid=psid) +obj.add_routes(dst='default', gateway='192.168.100.1', psid=psid, oif_name=ifs['int_if']) +#ns.cleanup(psid) diff --git a/dockyard/engine/common/network/drivers/bridges/utils.py b/dockyard/engine/common/network/drivers/bridges/utils.py new file mode 100644 index 0000000..eeb2829 --- /dev/null +++ b/dockyard/engine/common/network/drivers/bridges/utils.py @@ -0,0 +1,32 @@ +import abc +import random +import string + +class Random(object): + def __init__(self): + assert(False) + + @abc.abstractmethod + def get_number(self, length): + """This number generate a random string from of given + length. + """ + +class RandomNumber(Random): + """This class generate random string. + """ + def __init__(self): + pass + + def get_number(self, length): + """This method returns a random string of length. + random strings can be in lower case, upper case + and digits. + """ + choices = (string.ascii_lowercase + string.ascii_uppercase + + string.digits) + + rand = (''.join(random.SystemRandom().choice(choices) + for _ in range(length))) + + return rand diff --git a/dockyard/engine/common/network/manager.py b/dockyard/engine/common/network/manager.py new file mode 100644 index 0000000..6019085 --- /dev/null +++ b/dockyard/engine/common/network/manager.py @@ -0,0 +1,176 @@ +import importlib +from oslo_config import cfg + +from dockyard.engine.common.utils import json_dump, str_to_dict +from dockyard.engine.common.exceptions import InterfaceNotFound + +CONF = cfg.CONF +# Fetch scheduler defined in the configuration file and load it. +network_driver_info = CONF.network.network_driver +# May be this path can be specified in the configuration file. +network_driver_loc = 'dockyard.engine.common.network.drivers' +network_driver_info = (('%s.%s') % (network_driver_loc, network_driver_info)) +module_name, class_name = network_driver_info.rsplit(".", 1) +class_ = getattr(importlib.import_module(module_name), class_name) + + +class IFManager(object): + """This class acts as middleware between drivers and requests. + """ + def __init__(self): + self.network = class_() + + def get_ifs(self, psid): + """This method returns all the interfaces of the docker container. + :params psid: process id the container. + docker inspect | grep Pid + + :returns: Number of interface in a docker container. + """ + if not psid: + msg = ("Invalid namespace, Namespace can not be None.") + return msg + + ifs = dict() + ifs[psid] = self.network.get_ifs(psid) + return ifs + + def attach_if(self, ifname, brname): + """This method attach interface to the bridge for external interface + :params ifname: interface name of the new container. + :params brname: bridge name to which container attach. + """ + self.network.attach_port(ifname=ifname, br_name=brname) + + def create(self, peer=None, ifname=None, kind='veth'): + """This method creates interfaces in the namespace. + :params ifname: Interface name, if not defined random + string will be chossen. + :params kind: Kind of the veth pair. + :params peer: peer interface name for this interface. + """ + ifs_names = dict() + ifs_names['interface_names'] = \ + self.network.create_link_pair(ifname=ifname, peer=peer, + kind=kind) + + return ifs_names + + def update(self, ifname, brname=None, psid=None, state=None): + """This method is responsible for attaching network interface to + a bridge or moving an interface to a namespace. + """ + if brname and psid and state: + msg = ("Operation not supprted") + return json_dump(msg) + + data = dict() + if brname: + data['port_info'] = self.network.attach_port(ifname=ifname, + br_name=brname) + else: + if psid: + psid = int(psid) + try: + data["interface_info"] = self.network.move_to_namespace( + ifname=ifname, psid=psid) + except: + pass + + if state: + data["state"] = self.network.change_state(ifname=ifname, psid=psid, + state=state) + + return data + +class IPManager(object): + def __init__(self): + self.ip = class_() + + def addr(self, ifname, address, mask, + psid, broadcast=None): + """This method assigns name in the network namespace of container. + :params ifname: network interface name + :params address: ip address to be assigned + :params mask: netmask for the network + :params psid: network namespace for the container. + :params broadcast: broadcast address + + :returns: returns. + """ + ips = dict() + ips['IP'] = self.ip.addr(ifname=ifname, address=address, + mask=int(mask), psid=int(psid), + broadcast=broadcast) + + return ips + +class RouteManager(object): + def __init__(self): + self.route = class_() + + def routes(self, ifname, gateway, psid, dst='default'): + """Adds route in the given namespace. + :params oif_name: interface name. + :params gateway: gateway to be added. + :params psid: process id of the docker to which gateway + has to added. + :params dst: destinations for the route. + + :returns: rotue information. + """ + routes = dict() + psid = int(psid) + routes["routes"] = self.route.add_routes(oif_name=ifname, dst=dst, + psid=psid, gateway=gateway) + + return routes + + +class DockyardNetworkManager(object): + """It handles all the dockyard networking specific settings. + """ + def __init__(self): + """Dockyard networking involve playing around with network interfaces + network routes and ip address. + """ + self.ip = IPManager() + self.route = RouteManager() + self.if_ = IFManager() + + def add_ip(self, ip, gateway, mask, psid): + """This method uses RouteManages, IPManager, IFManager to perform + required actions. + + :params ip: IP Address to be assigned to the container. + :params gateway: Gateway to be assigned to the container. + :params mask: netmask for the network address. + :params psid: process id for the docker container to which + this IP has to be assigned. + + :returns: returns information set to the container. + """ + # Create network inerfaces. + print "++++" + ifs = self.if_.create()["interface_names"] + + print "++++" + # Move network interfaces to the namespace + psid = int(psid) + self.if_.update(ifname=ifs["int_if"], psid=psid, state="up") + + print "++++" + # Assign IP address to the container + self.ip.addr(ifname=ifs["int_if"], psid=psid, address=ip, + mask=int(mask)) + + print "++++" + # Create routes for the newly added interface + self.route.routes(ifname=ifs["int_if"], psid=psid, gateway=gateway, + dst='default') + + print "++++" + self.if_.attach_if(ifname=ifs["ext_if"], brname=CONF.network.bridge) + + print "++++" + return "Gathered Information." diff --git a/dockyard/engine/common/utils.py b/dockyard/engine/common/utils.py new file mode 100644 index 0000000..69383be --- /dev/null +++ b/dockyard/engine/common/utils.py @@ -0,0 +1,13 @@ +import ast +import json +import netifaces + +def json_dump(data): + """This function dumps data into json format to send over the internet. + """ + return json.dumps(data) + +def str_to_dict(data): + """This function convert string to dict. + """ + return ast.literal_eval(data) diff --git a/dockyard/engine/controllers/__init__.py b/dockyard/engine/controllers/__init__.py new file mode 100644 index 0000000..692f9ea --- /dev/null +++ b/dockyard/engine/controllers/__init__.py @@ -0,0 +1,17 @@ +from oslo_config import cfg + +NETWORK_DRIVER_OPT = [ + cfg.StrOpt('network_driver', + default='bridges.linux.LinuxBridgeManager', + help='Network driver for the changing settings for network.') +] + +CONF = cfg.CONF +opt_group = cfg.OptGroup(name='network', + title='Group for the network values of dockyard api') +CONF.register_group(opt_group) +CONF.register_opts(NETWORK_DRIVER_OPT, opt_group) + + +CONF = cfg.CONF +CONF.register_opts(NETWORK_DRIVER_OPT, group='network') diff --git a/dockyard/engine/controllers/dockyard_controllers.py b/dockyard/engine/controllers/dockyard_controllers.py new file mode 100644 index 0000000..aca2d59 --- /dev/null +++ b/dockyard/engine/controllers/dockyard_controllers.py @@ -0,0 +1,26 @@ +from pecan.rest import RestController +from dockyard.engine.controllers.network import DockyardNetworkController +from dockyard.engine.controllers.interface import InterfaceController + +class DockyardEngineController(RestController): + """Controller for the Engine. + """ + + """This is the controller for the interface. + """ + interface = InterfaceController() + + """Controller for IPs. + """ + # ip = IPController() + + """Controller for routes. + """ + # routes = RoutesController() + + """This controller is for providing dockyard specific networking. + """ + networks = DockyardNetworkController() + + def __init__(self): + pass diff --git a/dockyard/engine/controllers/floatingip.py b/dockyard/engine/controllers/floatingip.py new file mode 100644 index 0000000..62fc392 --- /dev/null +++ b/dockyard/engine/controllers/floatingip.py @@ -0,0 +1,16 @@ +from pecan import expose, request +from pecan.rest import RestController + +from dockyard.engine.common.utils import str_to_dict +from dockyard.engine.common.network.manager import DockyardNetworkManager + +class FloatingIPController(RestController): + def __init__(self): + self.network = DockyardNetworkManager() + + @expose() + def post(self, id_): + """It handles floating IP assignment for dockyard containers. + """ + kwargs = str_to_dict(request.body) + return self.network.add_ip(**kwargs) diff --git a/dockyard/engine/controllers/interface.py b/dockyard/engine/controllers/interface.py new file mode 100644 index 0000000..fd591d5 --- /dev/null +++ b/dockyard/engine/controllers/interface.py @@ -0,0 +1,43 @@ +from pecan import expose, abort, request +from pecan.rest import RestController +from dockyard.engine.common.utils import str_to_dict +from dockyard.engine.common.network.manager import IFManager +from dockyard.engine.controllers.utils import do_not_expose + +class InterfaceController(RestController): + def __init__(self): + self.network = IFManager() + + @do_not_expose + @expose() + def post(self): + """This method is responsible for creating network interfaces. + """ + kwargs = str_to_dict(request.body) + return self.network.create(**kwargs) + + @do_not_expose + @expose() + def delete(self): + abort(404) + + @do_not_expose + @expose() + def put(self): + kwargs = str_to_dict(request.body) + return self.network.update(**kwargs) + + @do_not_expose + @expose() + def detach(self): + abort(404) + + # created this for testing purpose only + @do_not_expose + @expose() + def get_one(self, psid): + """This method returns networks interfaces in a container. + """ + psid = int(psid) + return self.network.get_ifs(psid=psid) + diff --git a/dockyard/engine/controllers/ip.py b/dockyard/engine/controllers/ip.py new file mode 100644 index 0000000..fbd442a --- /dev/null +++ b/dockyard/engine/controllers/ip.py @@ -0,0 +1,29 @@ +from pecan import expose, abort, request +from pecan.rest import RestController +from dockyard.engine.common.utils import str_to_dict +from dockyard.engine.common.network.manager import IPManager +from dockyard.engine.controllers.utils import do_not_expose + +class IPController(RestController): + def __init__(self): + self.ip = IPManager() + + @do_not_expose + @expose() + def post(self): + kwargs = str_to_dict(request.body) + return self.ip.addr(**kwargs) + + @do_not_expose + @expose() + def delete(self): + abort(404) + + @do_not_expose + @expose() + def get_one(self, psid=None): + """This method returns IPs allocated to a container. + """ + abort(404) + + diff --git a/dockyard/engine/controllers/network.py b/dockyard/engine/controllers/network.py new file mode 100644 index 0000000..1fe7a5e --- /dev/null +++ b/dockyard/engine/controllers/network.py @@ -0,0 +1,12 @@ +from pecan.rest import RestController +from dockyard.engine.controllers.floatingip import FloatingIPController +from dockyard.engine.controllers.interface import InterfaceController + +class DockyardNetworkController(RestController): + """This class expose dockyard network API. + """ + floatingip = FloatingIPController() + interface = InterfaceController() + + def __init__(self): + pass diff --git a/dockyard/engine/controllers/routes.py b/dockyard/engine/controllers/routes.py new file mode 100644 index 0000000..b9d9ea2 --- /dev/null +++ b/dockyard/engine/controllers/routes.py @@ -0,0 +1,28 @@ +from pecan import expose, abort, request +from pecan.rest import RestController + +from dockyard.engine.common.utils import str_to_dict +from dockyard.engine.common.network.manager import RouteManager +from dockyard.engine.controllers.utils import do_not_expose + +class RoutesController(RestController): + def __init__(self): + self.routes = RouteManager() + + @do_not_expose + @expose() + def delete(self): + abort(404) + + @do_not_expose + @expose() + def post(self): + kwargs = str_to_dict(request.body) + return self.routes.routes(**kwargs) + + @do_not_expose + @expose() + def get_one(self): + abort(404) + + diff --git a/dockyard/engine/controllers/utils.py b/dockyard/engine/controllers/utils.py new file mode 100644 index 0000000..333316b --- /dev/null +++ b/dockyard/engine/controllers/utils.py @@ -0,0 +1,6 @@ +from pecan import expose + +def do_not_expose(method): + def dont_expose(self, *args, **kwargs): + return self.method(*args, **kwargs) + return dont_expose diff --git a/dockyard/engine_client/__init__.py b/dockyard/engine_client/__init__.py new file mode 100644 index 0000000..162fbdd --- /dev/null +++ b/dockyard/engine_client/__init__.py @@ -0,0 +1,24 @@ +import importlib +from oslo_config import cfg + +# Add default values for the database to be used for dockyard. +DATABASE_OPT = [ + cfg.StrOpt("driver", + default="consul.consul_client.ConsulKV", + help="Database driver for dockyard"), + cfg.IntOpt('synchronization_time', + default=20, + help='Time after which data will be synced .') +] + +CONF = cfg.CONF +opt_group = cfg.OptGroup(name='database', + title='Group for database') +CONF.register_group(opt_group) +CONF.register_opts(DATABASE_OPT, opt_group) +db_driver_loc = 'dockyard.common.stores' +db_driver_info = (('%s.%s') % (db_driver_loc, CONF.database.driver)) +module_name, db = db_driver_info.rsplit(".", 1) +module = importlib.import_module(module_name) + +#import synchronizer diff --git a/dockyard/engine_client/api_server/__init__.py b/dockyard/engine_client/api_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine_client/api_server/api_server.py b/dockyard/engine_client/api_server/api_server.py new file mode 100644 index 0000000..3e60c69 --- /dev/null +++ b/dockyard/engine_client/api_server/api_server.py @@ -0,0 +1,80 @@ +# This client will send request to api server, it sends request to API server + +from dockyard.engine_client.base import EngineClient +from dockyard.engine_client.container import container +from dockyard.engine_client.image import image +from dockyard.engine_client.network import network +from dockyard.engine_client.volume import volume + +class ProcessorRouter(object): + def __init__(self): + self.container = container.ContainerRouter() + self.image = image.Image() + self.network = network.Network() + self.volume = volume.Volume() + + def _get_module(self, url): + modules = url.rsplit(':', 1)[-1].split('/') + + if len(url.rsplit()[-1].split()) == 3: + module = modules[3] + else: + module = modules[2] + + return module + + def _call_operation(self, obj, operation, url, **kwargs): + try: + return getattr(obj, "process")(url, operation=operation, **kwargs) + except AttributeError: + # Currently no preprocessor or post processor are being done + # therefor it is passed otherwise InvalidOperation Exception + # has to be raised + pass + + def containers(self, url, **kwargs): + """This method calls appropriate method of the for the + preprocessing task. + """ + operation = self._get_module(url) + return self._call_operation(self.container, operation, url, **kwargs) + + def images(self, url, **kwargs): + """This method calls appropriate method for pre processing of + images. + """ + operation = self._get_module(url) + return self._call_operation(self.image, operation, url, **kwargs) + + def networks(self, url, **kwargs): + """This method is responsible for calling method of prepocessing + of networks. + """ + operation = self._get_module(url) + return self._call_operation(self.network, operation, url, **kwargs) + + def volumes(self, url, **kwargs): + """This method is routes preprocessing tasks to the appropriate + method of a class for volumes related operations. + """ + operation = self._get_module(url) + return self._call_operation(self.volume, operation, url, **kwargs) + + +class APIServerEngineClient(EngineClient): + """This method sends request to engine client through api server. + API server is made to listen for dockyard related CLI. It receives + request and then perform operation. + """ + def __init__(self): + self.router = ProcessorRouter() + + def _get_module(self, url): + return url.rsplit(':', 1)[-1].split('/')[1] + + def process(self, url, **kwargs): + module = self._get_module(url) + try: + return getattr(self.router, module)(url, **kwargs) + except: + return diff --git a/dockyard/engine_client/base.py b/dockyard/engine_client/base.py new file mode 100644 index 0000000..c0797b3 --- /dev/null +++ b/dockyard/engine_client/base.py @@ -0,0 +1,20 @@ +# This module acts as a base class for the other clients. All the clients must +# inherit this class +import abc + +class EngineClient(object): + def __init__(self): + pass + + @abc.abstractmethod + def pre_process(self, **kwargs): + """This method is responsible for sending requests related to dockyard + before launching container. + """ + + @abc.abstractmethod + def post_process(self, **kwargs): + """This method is reponsible for sending request related to dockyard + after container has been created. + """ + pass diff --git a/dockyard/engine_client/container/__init__.py b/dockyard/engine_client/container/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine_client/container/container.py b/dockyard/engine_client/container/container.py new file mode 100644 index 0000000..ca13dde --- /dev/null +++ b/dockyard/engine_client/container/container.py @@ -0,0 +1,32 @@ +from dockyard.engine_client.container.pre_container import PreProcessor +from dockyard.engine_client.container.post_container import PostProcessor + +class ContainerRouter(object): + """This class takes care of post processing and pre processing + required for providing the functionality of dockyard. + """ + mapping = { + "pre": "pre_processor", + "post": "post_processor" + } + + def __init__(self): + self.pre_processor = PreProcessor() + self.post_processor = PostProcessor() + + def _call_operation(self, url, **kwargs): + try: + obj = getattr(self, ContainerRouter.mapping[kwargs["r_type"]]) + return getattr(obj, kwargs["operation"])(url, **kwargs) + except AttributeError: + # Currently no preprocessor or post processor are being done + # therefor it is passed otherwise InvalidOperation Exception + # has to be raised + pass + + def process(self, url, **kwargs): + """This method routes the request to appropriate method of the class + depending on the whether it is pre processing request or post + processing request. + """ + self._call_operation(url=url, **kwargs) diff --git a/dockyard/engine_client/container/post_container.py b/dockyard/engine_client/container/post_container.py new file mode 100644 index 0000000..f3573a7 --- /dev/null +++ b/dockyard/engine_client/container/post_container.py @@ -0,0 +1,16 @@ +from dockyard.common.stores.consul.consul_client import ConsulKV + + +class PostProcessor(object): + def __init__(self): + self.db = ConsulKV() + + def create(self, url, **kwargs): + """This method does the pre processing required for dockyard functionality. + """ + + def list(self, url, **kwargs): + """This method is list all the containers on different hosts + as well as one container. + """ + self.db diff --git a/dockyard/engine_client/container/pre_container.py b/dockyard/engine_client/container/pre_container.py new file mode 100644 index 0000000..4a9cedc --- /dev/null +++ b/dockyard/engine_client/container/pre_container.py @@ -0,0 +1,3 @@ +class PreProcessor(object): + def __init__(self): + pass diff --git a/dockyard/engine_client/image/__init__.py b/dockyard/engine_client/image/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine_client/image/image.py b/dockyard/engine_client/image/image.py new file mode 100644 index 0000000..021e2a9 --- /dev/null +++ b/dockyard/engine_client/image/image.py @@ -0,0 +1,3 @@ +class Image(object): + def __init__(self): + pass diff --git a/dockyard/engine_client/network/__init__.py b/dockyard/engine_client/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine_client/network/network.py b/dockyard/engine_client/network/network.py new file mode 100644 index 0000000..0f19b9f --- /dev/null +++ b/dockyard/engine_client/network/network.py @@ -0,0 +1,3 @@ +class Network(object): + def __init__(self): + pass diff --git a/dockyard/engine_client/synchronizer/__init__.py b/dockyard/engine_client/synchronizer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine_client/synchronizer/synchronizer.py b/dockyard/engine_client/synchronizer/synchronizer.py new file mode 100644 index 0000000..0b5ce2f --- /dev/null +++ b/dockyard/engine_client/synchronizer/synchronizer.py @@ -0,0 +1,34 @@ +import ast +import netifaces +from oslo_config import cfg + +from dockyard.engine_client import module, db + +CONF = cfg.CONF + + +class ContainerSynchronizer(object): + def __init__(self): + self.db = getattr(module, db)() + + + def synchronize(self, infos): + """This method is responsible for initilizing databases at the start + of dockyard. It collects all the containers running on localhost + and putt the data in the consul + """ + for info in infos: + self._sync(info) + + def _get_value(self, key): + """This method returns all the values corresponding to a + key and resturn. + """ + return self.db.get(key) + + def _sync(self, info): + """This method is responsible for registering a container. + """ + (key, value) = info + print info + return self.db.put(str(key), str(value)) diff --git a/dockyard/engine_client/volume/__init__.py b/dockyard/engine_client/volume/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dockyard/engine_client/volume/volume.py b/dockyard/engine_client/volume/volume.py new file mode 100644 index 0000000..d17ea02 --- /dev/null +++ b/dockyard/engine_client/volume/volume.py @@ -0,0 +1,3 @@ +class Volume(object): + def __init__(self): + pass diff --git a/dockyard/model/__init__.py b/dockyard/model/__init__.py new file mode 100644 index 0000000..d983f7b --- /dev/null +++ b/dockyard/model/__init__.py @@ -0,0 +1,15 @@ +from pecan import conf # noqa + + +def init_model(): + """ + This is a stub method which is called at application startup time. + + If you need to bind to a parsed database configuration, set up tables or + ORM classes, or perform any database initialization, this is the + recommended place to do it. + + For more information working with databases, and some common recipes, + see http://pecan.readthedocs.org/en/latest/databases.html + """ + pass diff --git a/dockyard/tests/__init__.py b/dockyard/tests/__init__.py new file mode 100644 index 0000000..78ea527 --- /dev/null +++ b/dockyard/tests/__init__.py @@ -0,0 +1,22 @@ +import os +from unittest import TestCase +from pecan import set_config +from pecan.testing import load_test_app + +__all__ = ['FunctionalTest'] + + +class FunctionalTest(TestCase): + """ + Used for functional tests where you need to test your + literal application and its integration with the framework. + """ + + def setUp(self): + self.app = load_test_app(os.path.join( + os.path.dirname(__file__), + 'config.py' + )) + + def tearDown(self): + set_config({}, overwrite=True) diff --git a/dockyard/tests/config.py b/dockyard/tests/config.py new file mode 100644 index 0000000..4d41a1d --- /dev/null +++ b/dockyard/tests/config.py @@ -0,0 +1,25 @@ +# Server Specific Configurations +server = { + 'port': '8080', + 'host': '0.0.0.0' +} + +# Pecan Application Configurations +app = { + 'root': 'dockyard.controllers.root.RootController', + 'modules': ['dockyard'], + 'static_root': '%(confdir)s/../../public', + 'template_path': '%(confdir)s/../templates', + 'debug': True, + 'errors': { + '404': '/error/404', + '__force_dict__': True + } +} + +# Custom Configurations must be in Python dictionary format:: +# +# foo = {'bar':'baz'} +# +# All configurations are accessible at:: +# pecan.conf diff --git a/dockyard/tests/test_functional.py b/dockyard/tests/test_functional.py new file mode 100644 index 0000000..b33259c --- /dev/null +++ b/dockyard/tests/test_functional.py @@ -0,0 +1,22 @@ +from unittest import TestCase +from webtest import TestApp +from dockyard.tests import FunctionalTest + + +class TestRootController(FunctionalTest): + + def test_get(self): + response = self.app.get('/') + assert response.status_int == 200 + + def test_search(self): + response = self.app.post('/', params={'q': 'RestController'}) + assert response.status_int == 302 + assert response.headers['Location'] == ( + 'http://pecan.readthedocs.org/en/latest/search.html' + '?q=RestController' + ) + + def test_get_not_found(self): + response = self.app.get('/a/bogus/url', expect_errors=True) + assert response.status_int == 404 diff --git a/dockyard/tests/test_units.py b/dockyard/tests/test_units.py new file mode 100644 index 0000000..573fb68 --- /dev/null +++ b/dockyard/tests/test_units.py @@ -0,0 +1,7 @@ +from unittest import TestCase + + +class TestUnits(TestCase): + + def test_units(self): + assert 5 * 5 == 25 diff --git a/etc/dockyard/dockyard.conf b/etc/dockyard/dockyard.conf new file mode 100644 index 0000000..2819807 --- /dev/null +++ b/etc/dockyard/dockyard.conf @@ -0,0 +1,54 @@ +[default] +# host parameter defines the IP address on the server machine, to +# be used for binding to serve request. +host = 0.0.0.0 + +# port parameter defines the port on which dockyard server will keep +# on listening for REST APIs. +port = 5869 + +# Scheduler to be used for launching the containers and for other load +# task. +scheduler = round_robin.RoundRobinScheduler + +# For managment of the members, we must use some kind of membership management +# tool. There are support for consul, ..... . +membership = consul_driver.Consul + +# Type of the agent, it is based on the role being taken by the package +# This product can behave differently based on the role being given by it. +agent = master + +[network] + +# Network driver defines the way networking is being handled by dockyard. +network_driver = bridges.linux.LinuxBridgeManager + +bridge = br + +[consul] +service_name = 'dockyard' + +[docker] +# IP address to which docker container is binded. +docker_host = 0.0.0.0 + +# Port number on which docker container is listening. +docker_port = 2375 + +# Docker service name, it is given here because may be consul is used +# by other application also therefor dockyard can register under different +# service name +docker_name = 'docker' + +[database] +# Database driver to be used for storing data. +driver = consul.consul_client.ConsulKV + +[consul_database] +# This is the time after which synchronizer thread will wake up and start +# syncing containers launched. +synchronization_time = 20 + +#Secret used to put data into the consul and to retrieve from the database +secret = secret_information diff --git a/requirement.txt b/requirement.txt new file mode 100644 index 0000000..eb88aa7 --- /dev/null +++ b/requirement.txt @@ -0,0 +1,8 @@ +oslo.log +oslo.config +pecan +python-consul +nsenter +pyroute2 +netifaces +python-dev diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..aa7fc1f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,6 @@ +[nosetests] +match=^test +where=dockyard +nocapture=1 +cover-package=dockyard +cover-erase=1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e8a5ab0 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +try: + from setuptools import setup, find_packages +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +setup( + name='dockyard', + version='0.1', + description='', + author='', + author_email='', + install_requires=[ + "pecan", + ], + test_suite='dockyard', + zip_safe=False, + include_package_data=True, + packages=find_packages(exclude=['ez_setup']) +) diff --git a/test/consul_client b/test/consul_client new file mode 100644 index 0000000..fcbf24c --- /dev/null +++ b/test/consul_client @@ -0,0 +1 @@ +consul agent -data-dir /tmp/consul -node=agent-two -bind=$1 -config-dir /etc/consul.d diff --git a/test/consul_server b/test/consul_server new file mode 100644 index 0000000..e91e422 --- /dev/null +++ b/test/consul_server @@ -0,0 +1 @@ +consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=agent-one -bind=$1 -config-dir /etc/consul.d diff --git a/test/floatingip.py b/test/floatingip.py new file mode 100644 index 0000000..58a688b --- /dev/null +++ b/test/floatingip.py @@ -0,0 +1 @@ +curl http://127.0.0.1:5869/dockyard/networks/floatingip/4162 -XPOST -d '{"psid": "4162", "ip": "10.0.9.2", "mask": "24", "gateway": "10.0.9.1"}' diff --git a/test/resize b/test/resize new file mode 100644 index 0000000..b5726ae --- /dev/null +++ b/test/resize @@ -0,0 +1 @@ +curl localhost:5869/containers/$1/resize?h=40&w=80 diff --git a/test/test_engine.sh b/test/test_engine.sh new file mode 100644 index 0000000..9a2a13a --- /dev/null +++ b/test/test_engine.sh @@ -0,0 +1,10 @@ +count=2 +psid=$1 + +while [ $count -lt 254 ] +do + ip="10.0.9.$count" + abc=`curl http://10.0.9.1:5869/dockyard/network -XPOST -d "{'psid': '$psid', 'ip': '$ip', 'gateway': '10.0.9.1', 'mask': '24'}"` + count=`expr $count + 1` + echo "adding ..... $ip" +done diff --git a/test/update b/test/update new file mode 100644 index 0000000..07753ea --- /dev/null +++ b/test/update @@ -0,0 +1 @@ +curl -XPOST -H 'Content-Type: application/json' localhost:5869/containers/$1/update -d '{ "CpuShares": 512 }'