You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by an...@apache.org on 2016/10/08 02:30:37 UTC

[06/27] libcloud git commit: initial push

initial push


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/8495ea71
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/8495ea71
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/8495ea71

Branch: refs/heads/trunk
Commit: 8495ea71744394528f30e3e297ea41a2e22574bb
Parents: a37a02a
Author: Mario Loria <ma...@arroyonetworks.com>
Authored: Tue Sep 27 23:12:46 2016 -0400
Committer: Anthony Shaw <an...@apache.org>
Committed: Sat Oct 8 13:29:22 2016 +1100

----------------------------------------------------------------------
 libcloud/container/drivers/rancher.py | 648 +++++++++++++++++++++++++++++
 libcloud/container/providers.py       |   2 +
 libcloud/container/types.py           |   1 +
 3 files changed, 651 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/8495ea71/libcloud/container/drivers/rancher.py
----------------------------------------------------------------------
diff --git a/libcloud/container/drivers/rancher.py b/libcloud/container/drivers/rancher.py
new file mode 100644
index 0000000..5229442
--- /dev/null
+++ b/libcloud/container/drivers/rancher.py
@@ -0,0 +1,648 @@
+import base64
+import datetime
+import shlex
+import re
+
+try:
+    import simplejson as json
+except:
+    import json
+
+from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import b
+
+from libcloud.common.base import JsonResponse, ConnectionUserAndKey
+from libcloud.common.types import InvalidCredsError
+
+from libcloud.container.base import (Container, ContainerDriver,
+                                     ContainerImage)
+
+from libcloud.container.providers import Provider
+from libcloud.container.types import ContainerState
+
+VALID_RESPONSE_CODES = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
+                        httplib.NO_CONTENT]
+
+
+class RancherResponse(JsonResponse):
+
+    def parse_body(self):
+        if len(self.body) == 0 and not self.parse_zero_length_body:
+            return self.body
+        valid_content_types = ['application/json',
+                               'application/json; charset=utf-8']
+        content_type = self.headers.get('content-type')
+        if content_type in valid_content_types:
+            return json.loads(self.body)
+
+    def parse_error(self):
+        if self.status == 401:
+            raise InvalidCredsError('Invalid credentials')
+        return self.body
+
+    def success(self):
+        return self.status in VALID_RESPONSE_CODES
+
+
+class RancherException(Exception):
+
+    def __init__(self, code, message):
+        self.code = code
+        self.message = message
+        self.args = (code, message)
+
+    def __str__(self):
+        return "%s %s" % (self.code, self.message)
+
+    def __repr__(self):
+        return "RancherException %s %s" % (self.code, self.message)
+
+
+class RancherConnection(ConnectionUserAndKey):
+
+    responseCls = RancherResponse
+    timeout = 30
+
+    def add_default_headers(self, headers):
+        """
+        Add parameters that are necessary for every request
+        If user and password are specified, include a base http auth
+        header
+        """
+        headers['Content-Type'] = 'application/json'
+        headers['Accept'] = 'application/json'
+        if self.key and self.user_id:
+            user_b64 = base64.b64encode(b('%s:%s' % (self.user_id, self.key)))
+            headers['Authorization'] = 'Basic %s' % (user_b64.decode('utf-8'))
+        return headers
+
+
+class RancherContainerDriver(ContainerDriver):
+
+    type = Provider.RANCHER
+    name = 'Rancher'
+    website = 'http://rancher.com'
+    connectionCls = RancherConnection
+    # Holding off on this for now. Only Environment API interaction enabled.
+    supports_clusters = False
+    # As in the /v1/
+    version = '1'
+
+    def __init__(self, key, secret, secure=False, host='localhost',
+                 port=80, key_file=None, cert_file=None):
+        """
+        :param    key: API key or username to used (required)
+        :type     key: ``str``
+
+        :param    secret: Secret password to be used (required)
+        :type     secret: ``str``
+
+        :param    secure: Whether to use HTTPS or HTTP. Note: Some providers
+                only support HTTPS, and it is on by default.
+        :type     secure: ``bool``
+
+        :param    host: Override hostname used for connections.
+        :type     host: ``str``
+
+        :param    port: Override port used for connections.
+        :type     port: ``int``
+
+        :param    key_file: Path to private key for TLS connection (optional)
+        :type     key_file: ``str``
+
+        :param    cert_file: Path to public key for TLS connection (optional)
+        :type     cert_file: ``str``
+
+        :return: ``None``
+        """
+        super(RancherContainerDriver, self).__init__(key=key, secret=secret,
+                                                     secure=secure, host=host,
+                                                     port=port,
+                                                     key_file=key_file,
+                                                     cert_file=cert_file)
+        if host.startswith('https://'):
+            secure = True
+
+        # strip the prefix
+        prefixes = ['http://', 'https://']
+        for prefix in prefixes:
+            if host.startswith(prefix):
+                host = host.strip(prefix)
+        """
+        if key_file or cert_file:
+            # docker tls authentication-
+            # https://docs.docker.com/articles/https/
+            # We pass two files, a key_file with the
+            # private key and cert_file with the certificate
+            # libcloud will handle them through LibcloudHTTPSConnection
+            if not (key_file and cert_file):
+                raise Exception(
+                    'Needs both private key file and '
+                    'certificate file for tls authentication')
+            self.connection.key_file = key_file
+            self.connection.cert_file = cert_file
+            self.connection.secure = True
+        else:
+            self.connection.secure = secure
+        """
+
+        self.connection.host = host
+        self.connection.port = port
+
+        # We only support environment api keys, meaning none of this:
+        # self.baseuri = "/v%s/projects/%s" % (self.version, project_id)
+        self.baseuri = "/v%s" % self.version
+
+    def ex_list_environments(self):
+        """
+        List the deployed container images
+        :param image: Filter to containers with a certain image
+        :type  image: :class:`libcloud.container.base.ContainerImage`
+        :param all: Show all container (including stopped ones)
+        :type  all: ``bool``
+        :rtype: ``list`` of :class:`libcloud.container.base.Container`
+        """
+
+        result = self.connection.request("%s/environments" % self.baseuri).object
+        return result['data']
+
+    def ex_deploy_environment(self, name, description=None, dockercompose=None,
+                              environment=None, externalid=None, outputs=None,
+                              previousenvironment=None, previousexternalid=None,
+                              ranchercompose=None, start=True):
+
+        payload = {
+            "description": description,
+            "dockerCompose": dockercompose,
+            "environment": environment,
+            "externalId": externalid,
+            "name": name,
+            "outputs": outputs,
+            "previousEnvironment": previousenvironment,
+            "previousExternalId": previousexternalid,
+            "rancherCompose": ranchercompose,
+            "startOnCreate": start
+        }
+        data = json.dumps({k: v for k, v in payload.items() if v is not None})
+        result = self.connection.request('%s/environments' %
+                                         self.baseuri, data=data,
+                                         method='POST').object
+
+        return result
+
+    def ex_get_environment(self, env_id):
+        """
+        Get a service by ID
+        :param env_id: The environment to be obtained.
+        :return: The API dict object returned for the new service.
+        """
+        result = self.connection.request("%s/environments/%s" %
+                                         (self.baseuri, env_id)).object
+
+        return result
+
+    def ex_destroy_environment(self, env_id):
+
+        result = self.connection.request('%s/environments/%s' % (
+                                         self.baseuri, env_id), method='DELETE')
+        if result.status in VALID_RESPONSE_CODES:
+            return result.status in VALID_RESPONSE_CODES
+        else:
+            raise RancherException(result.status,
+                                   'failed to destroy environment')
+
+    def ex_activate_environment(self, env_id):
+
+        result = self.connection.request('%s/environments/%s?action=activateservices' %
+                                         (self.baseuri, env_id),
+                                         method='POST')
+        if result.status in VALID_RESPONSE_CODES:
+            return result.status in VALID_RESPONSE_CODES
+        else:
+            raise RancherException(result.status,
+                                   'failed to activate environment')
+
+    def ex_deactivate_environment(self, env_id):
+
+        result = self.connection.request('%s/environments/%s?action=deactivateservices' %
+                                         (self.baseuri, env_id),
+                                         method='POST')
+        if result.status in VALID_RESPONSE_CODES:
+            return result.status in VALID_RESPONSE_CODES
+        else:
+            raise RancherException(result.status,
+                                   'failed to deactivate environment')
+
+    def build_payload(self, image, start=True, name=None, image_type="docker",
+                      blkiodeviceoptions=None, build=None,
+                      capadd=None,
+                      capdrop=None, command=None,
+                      count=None, cpuset=None, cpushares=None,
+                      datavolumemounts=None, datavolumes=None,
+                      datavolumesfrom=None, description=None, devices=None,
+                      dns=None, dnssearch=None, domainname=None,
+                      entrypoint=None, environment=None, expose=None,
+                      extrahosts=None, healthcheck=None, hostname=None,
+                      instancelinks=None, labels=None, logconfig=None,
+                      lxcconf=None, memory=None, memoryswap=None,
+                      networkcontainerrid=None, networkids=None,
+                      networkmode=None, pidmode=None, ports=None,
+                      privileged=None, publishallports=None,
+                      readonly=None, registrycredentialid=None,
+                      requestedhostid=None, restartpolicy=None,
+                      securityopt=None,
+                      stdinopen=None, tty=None, user=None,
+                      volumedriver=None, workingdir=None):
+
+        if command is not None:
+            command = shlex.split(str(command))
+
+        if image.version is not None:
+            imageuuid = image_type + ':' + image.path + ':' + image.version
+        else:
+            imageuuid = image_type + ':' + image.path
+
+        payload = {
+            "blkioDeviceOptions": blkiodeviceoptions,
+            "build": build,
+            "capAdd": capadd,
+            "capDrop": capdrop,
+            "command": command,
+            "count": count,
+            "cpuSet": cpuset,
+            "cpuShares": cpushares,
+            "dataVolumeMounts": datavolumemounts,
+            "dataVolumes": datavolumes,
+            "dataVolumesFrom": datavolumesfrom,
+            "description": description,
+            "devices": devices,
+            "dns": dns,
+            "dnsSearch": dnssearch,
+            "domainName": domainname,
+            "entryPoint": entrypoint,
+            "environment": environment,
+            "expose": expose,
+            "extraHosts": extrahosts,
+            "healthCheck": healthcheck,
+            "hostname": hostname,
+            "imageUuid": imageuuid,
+            "instanceLinks": instancelinks,
+            "labels": labels,
+            "logConfig": logconfig,
+            "lxcConf": lxcconf,
+            "memory": memory,
+            "memorySwap": memoryswap,
+            "name": name,
+            "networkContainerId": networkcontainerrid,
+            "networkIds": networkids,
+            "networkMode": networkmode,
+            "pidMode": pidmode,
+            "ports": ports,
+            "privileged": privileged,
+            "publishAllPorts": publishallports,
+            "readOnly": readonly,
+            "registryCredentialId": registrycredentialid,
+            "requestedHostId": requestedhostid,
+            "restartPolicy": restartpolicy,
+            "securityOpt": securityopt,
+            "startOnCreate": start,
+            "stdinOpen": stdinopen,
+            "tty": tty,
+            "user": user,
+            "volumeDriver": volumedriver,
+            "workingdir": workingdir
+        }
+
+        return payload
+
+    def ex_list_services(self):
+        """
+        List the deployed container images
+        :param image: Filter to containers with a certain image
+        :type  image: :class:`libcloud.container.base.ContainerImage`
+        :param all: Show all container (including stopped ones)
+        :type  all: ``bool``
+        :rtype: ``list`` of :class:`libcloud.container.base.Container`
+        """
+
+        result = self.connection.request("%s/services" % self.baseuri).object
+        return result['data']
+
+    def ex_deploy_service(self, name, image, environmentid, start=True,
+                          assignserviceipaddress=None, service_description=None,
+                          externalid=None, metadata=None, retainip=None,
+                          scale=None, scalepolicy=None,
+                          secondarylaunchconfigs=None, selectorcontainer=None,
+                          selectorlink=None,
+                          vip=None, datavolumesfromlaunchconfigs=None,
+                          disks=None, kind=None, memorymb=None,
+                          networklaunchconfig=None, requsetedipaddress=None,
+                          userdata=None, vcpu=None, **kwargs):
+        """
+        Deploy a Rancher Service under a stack.
+        :param name: Name of the service.
+        :param image: ContainerImage object of image to utilize.
+        :param environmentid: The environment/stack ID this service is tied to.
+        :param kwargs: The Launchconfig provided as top-level options,
+        similar to deploy_container.
+        :return: The API object returned on proper service creation.
+        """
+
+        service_specific_container_config = {
+            "dataVolumesFromLaunchConfigs": datavolumesfromlaunchconfigs,
+            "disks": disks,
+            "kind": kind,
+            "memoryMb": memorymb,
+            "networkLaunchConfig": networklaunchconfig,
+            "requestedIpAddress": requsetedipaddress,
+            "userdata": userdata,
+            "vcpu": vcpu
+        }
+
+        # Build the de-facto container payload
+        # Note that we don't need to remove "name" since its None by default.
+        launchconfig = self.build_payload(image, start, **kwargs)
+        # Add in extra service configuration
+        launchconfig.update(service_specific_container_config)
+        launchconfig = json.dumps({k: v for k, v in launchconfig.items()
+                                   if v is not None})
+        service_payload = {
+            "assignServiceIpAddress": assignserviceipaddress,
+            "description": service_description,
+            "environmentId": environmentid,
+            "externalId": externalid,
+            "launchConfig": json.loads(launchconfig),
+            "metadata": metadata,
+            "name": name,
+            "retainIp": retainip,
+            "scale": scale,
+            "scalePolicy": scalepolicy,
+            "secondaryLaunchConfigs": secondarylaunchconfigs,
+            "selectorContainer": selectorcontainer,
+            "selectorLink": selectorlink,
+            "startOnCreate": start,
+            "vip": vip
+        }
+
+        data = json.dumps({k: v for k, v in service_payload.items()
+                           if v is not None})
+        result = self.connection.request('%s/services' % self.baseuri,
+                                         data=data, method='POST').object
+
+        return result
+
+    def ex_get_service(self, service_id):
+        """
+        Get a service by ID
+        :param service_id: The service to be obtained.
+        :return: The API dict object returned for the new service.
+        """
+        result = self.connection.request("%s/services/%s" %
+                                         (self.baseuri, service_id)).object
+
+        return result
+
+    def ex_destroy_service(self, service_id):
+        """
+        Delete a service.
+        :param service_id: The service to be destroyed
+        :return: True if the destroy was successful, False otherwise.
+        :rtype: ``bool``
+        """
+        result = self.connection.request('%s/services/%s' % (self.baseuri,
+                                         service_id), method='DELETE')
+        if result.status in VALID_RESPONSE_CODES:
+            return result.status in VALID_RESPONSE_CODES
+        else:
+            raise RancherException(result.status,
+                                   'failed to destroy service')
+
+    def ex_activate_service(self, service_id):
+
+        result = self.connection.request('%s/services/%s?action=activate' %
+                                         (self.baseuri, service_id),
+                                         method='POST')
+        if result.status in VALID_RESPONSE_CODES:
+            return result.status in VALID_RESPONSE_CODES
+        else:
+            raise RancherException(result.status,
+                                   'failed to activate service')
+
+    def ex_deactivate_service(self, service_id):
+
+        result = self.connection.request('%s/services/%s?action=deactivate' %
+                                         (self.baseuri, service_id),
+                                         method='POST')
+        if result.status in VALID_RESPONSE_CODES:
+            return result.status in VALID_RESPONSE_CODES
+        else:
+            raise RancherException(result.status,
+                                   'failed to deactivate service')
+
+    def list_containers(self, image=None, all=True):
+        """
+        List the deployed container images
+        :param image: Filter to containers with a certain image
+        :type  image: :class:`libcloud.container.base.ContainerImage`
+        :param all: Show all container (including stopped ones)
+        :type  all: ``bool``
+        :rtype: ``list`` of :class:`libcloud.container.base.Container`
+        """
+
+        result = self.connection.request("%s/containers" % self.baseuri).object
+        containers = [self._to_container(value) for value in result['data']]
+        return containers
+
+    def deploy_container(self, name, image, parameters=None, start=True,
+                         **kwargs):
+        """
+        Deploy an installed container image
+        For details on the additional parameters see : http://bit.ly/1PjMVKV
+        :param name: The name of the new container
+        :type  name: ``str``
+        :param image: The container image to deploy
+        :type  image: :class:`libcloud.container.base.ContainerImage`
+        :param parameters: Container Image parameters
+        :type  parameters: ``str``
+        :param start: Start the container on deployment
+        :type  start: ``bool``
+        :rtype: :class:`Container`
+        """
+        payload = self.build_payload(name, image, start, **kwargs)
+        data = json.dumps({k: v for k, v in payload.items() if v is not None})
+
+        result = self.connection.request('%s/containers' % self.baseuri,
+                                         data=data, method='POST').object
+
+        return self._to_container(result)
+
+    def get_container(self, id):
+        """
+        Get a container by ID
+        :param id: The ID of the container to get
+        :type  id: ``str``
+        :rtype: :class:`libcloud.container.base.Container`
+        """
+        result = self.connection.request("%s/containers/%s" %
+                                         (self.baseuri, id)).object
+
+        return self._to_container(result)
+
+    def stop_container(self, container):
+        """
+        Stop a container
+        :param container: The container to be stopped
+        :type  container: :class:`libcloud.container.base.Container`
+        :return: The container refreshed with current data
+        :rtype: :class:`libcloud.container.base.Container`
+        """
+        result = self.connection.request('%s/containers/%s?action=stop' %
+                                         (self.baseuri, container.id),
+                                         method='POST')
+        if result.status in VALID_RESPONSE_CODES:
+            return self.get_container(container.id)
+        else:
+            raise RancherException(result.status,
+                                  'failed to stop container')
+
+    def destroy_container(self, container):
+        """
+        Remove a container
+        :param container: The container to be destroyed
+        :type  container: :class:`libcloud.container.base.Container`
+        :return: True if the destroy was successful, False otherwise.
+        :rtype: ``bool``
+        """
+        result = self.connection.request('%s/containers/%s' % (self.baseuri,
+                                         container.id), method='DELETE')
+        if result.status in VALID_RESPONSE_CODES:
+            return self.get_container(container.id)
+        else:
+            raise RancherException(result.status,
+                                  'failed to destroy container')
+
+    def _gen_image(self, imageuuid):
+        """
+        This function converts a valid Rancher `imageUuid` string to a valid
+        image object. Only supports docker based images hence `docker:` must
+        prefix!!
+
+        For a imageuuid:
+            docker:<hostname>:<port>/<namespace>/<imagename>:<version>
+
+        The following applies:
+            id = <imagename>
+            name = <imagename>
+            path = <hostname>:<port>/<namespace>/<imagename>
+            version = <version>
+
+        :param imageUuid: A valid Rancher image string
+        i.e. `docker:rlister/hastebin:8.0`
+        :return: Proper ContainerImage object.
+        """
+        # Obtain just the name(:version) for parsing
+        if '/' not in imageuuid:
+            # String looks like `docker:mysql:8.0`
+            image_name_version = imageuuid.partition(':')[2]
+        else:
+            # String looks like `docker:oracle/mysql:8.0`
+            image_name_version = imageuuid.rpartition("/")[2]
+        # Parse based on ':'
+        if ':' in image_name_version:
+            version = image_name_version.partition(":")[2]
+            id = image_name_version.partition(":")[0]
+            name = id
+        else:
+            version = 'latest'
+            id = image_name_version
+            name = id
+        # Get our path based on if there was a version
+        if version != 'latest':
+            path = imageuuid.partition(':')[2].rpartition(':')[0]
+        else:
+            path = imageuuid.partition(':')[2]
+
+        return ContainerImage(
+            id=id,
+            name=name,
+            path=path,
+            version=version,
+            driver=self.connection.driver,
+            extra={
+                "imageUuid": imageuuid
+            }
+        )
+
+    def _to_container(self, data):
+        """
+        Convert container in proper Container instance object
+        ** Updating is NOT supported!!
+
+        :param data: API data about container i.e. result.object
+        :return: Proper Container object:
+         see http://libcloud.readthedocs.io/en/latest/container/api.html
+
+        """
+        rancher_state = data['state']
+        if 'running' in rancher_state:
+            state = ContainerState.RUNNING
+        elif 'stopped' in rancher_state:
+            state = ContainerState.STOPPED
+        elif 'restarting' in rancher_state:
+            state = ContainerState.REBOOTING
+        elif 'error' in rancher_state:
+            state = ContainerState.ERROR
+        elif 'removed' or 'purged' in rancher_state:
+            # A Removed container is purged after x amt of time.
+            # Both of these render the container dead (can't be started later)
+            state = ContainerState.TERMINATED
+        elif rancher_state.endswith('ing'):
+            # Best we can do for current actions
+            state = ContainerState.PENDING
+        else:
+            state = ContainerState.UNKNOWN
+
+        # Includes the most used items atm. Eventually, everything ;)
+        extra = {
+            "state": rancher_state,
+            "command": data['command'],
+            "created": data['created'],
+            "dataVolumes": data['dataVolumes'],
+            "dns": data['dns'],
+            "dnsSearch": data['dnsSearch'],
+            "domainName": data['domainName'],
+            "entryPoint": data['entryPoint'],
+            "environment": data['environment'],
+            "expose": data['expose'],
+            "healthState": data['healthState'],
+            "hostId": data['hostId'],
+            "hostname": data['hostname'],
+            "labels": data['labels'],
+            "networkMode": data['networkMode'],
+            "ports": data['ports'],
+            "primaryIpAddress": data['primaryIpAddress'],
+            "privileged": data['privileged'],
+            "restartPolicy": data['restartPolicy'],
+            "stdinOpen": data['stdinOpen'],
+            "tty": data['tty'],
+            "uuid": data['uuid'],
+            "workingDir": data['workingDir']
+        }
+
+        return Container(
+            id=data['id'],
+            name=data['name'],
+            image=self._gen_image(data['imageUuid']),
+            ip_addresses=[data['primaryIpAddress']],
+            state=state,
+            driver=self.connection.driver,
+            extra=extra)
+
+
+def ts_to_str(timestamp):
+    """
+    Return a timestamp as a nicely formated datetime string.
+    """
+    date = datetime.datetime.fromtimestamp(timestamp)
+    date_string = date.strftime("%d/%m/%Y %H:%M %Z")
+    return date_string

http://git-wip-us.apache.org/repos/asf/libcloud/blob/8495ea71/libcloud/container/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/container/providers.py b/libcloud/container/providers.py
index 16ab58c..a823382 100644
--- a/libcloud/container/providers.py
+++ b/libcloud/container/providers.py
@@ -28,6 +28,8 @@ DRIVERS = {
     ('libcloud.container.drivers.ecs', 'ElasticContainerDriver'),
     Provider.KUBERNETES:
     ('libcloud.container.drivers.kubernetes', 'KubernetesContainerDriver'),
+    Provider.RANCHER:
+    ('libcloud.container.drivers.rancher', 'RancherContainerDriver'),
 }
 
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/8495ea71/libcloud/container/types.py
----------------------------------------------------------------------
diff --git a/libcloud/container/types.py b/libcloud/container/types.py
index bddca7f..b89fdfd 100644
--- a/libcloud/container/types.py
+++ b/libcloud/container/types.py
@@ -51,6 +51,7 @@ class Provider(object):
     ECS = 'ecs'
     JOYENT = 'joyent'
     KUBERNETES = 'kubernetes'
+    RANCHER = 'rancher'
 
 
 class ContainerState(Type):