New issue

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

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

Already on GitHub? Sign in to your account

New Rancher Container Driver #876

Closed
wants to merge 25 commits into
base: trunk
from

Conversation

Projects
None yet
5 participants
@InAnimaTe
Contributor

InAnimaTe commented Sep 29, 2016

Rancher Container Driver

Description

This is a new Container Driver for Rancher, the fantastic container orchestration platform that supports the likes of Kubernetes, Docker Swarm, and Mesos frameworks. It provides a fantastic abstraction layer for working with these frameworks on any infrastructure you'd like! Plus it's open-source!

Utilizing the v1 API and extensively working with the docs, I've managed to get an initial version of a fairly reliable ContainerDriver for Libcloud. It follows the normal Container conventions as closely as possible and provides extensive docstrings for excellent interaction, even without a massive understanding of Rancher itself.

The three main top-level Rancher items are implemented:

  • Containers
  • Services (ex_*)
  • Environments (ex_*)

Although not every possible action is available, the necessities like creation and deletion plus things like activation, get, search, and list are included.

As you review this, please ask questions if you aren't sure about how something is implemented!

Background

With Arroyo Networks' upcoming NaaS platform, Inflow, I've been hard at work establishing our Infrastructure assets. Rancher has seemed a clear choice to help us maintain a nimble platform not tied to any specific back-end Infrastructure provider. Hence, much of our Python code needs a way to interact with our clusters, so writing a Libcloud plugin was pretty much a no-brainer.

Example

from libcloud.container.providers import get_driver
from libcloud.container.types import Provider

driver = get_driver(Provider.RANCHER)
connection = driver(key="ACCESS_KEY_HERE", secret="SECRET_KEY_HERE", host="172.30.0.100", port=8080)

image = ContainerImage("hastebin", "hastebin", "rlister/hastebin", "latest", driver=None) 
newcontainer = connection.deploy_container("myawesomepastebin", image, environment={"STORAGE_TYPE": "file"})

Status

  • done, ready for review

I have tested this works appropriately with Rancher Server v1.1.3 (API v1).

Of course, I want persons looking at this to pay attention to the whole PR. However, there are a few key areas I want some focus on:

  • Unnecessary sections (do I really need parse_body?)
  • Inefficient handling (is there a better way to do Image parsing or TLS/SSL support?)
  • Error Handling looks/works sanely.
  • Convention adherence (I'm a newer programmer; I may not know well established *ism's)

I encourage reviewers to pull my branch and play with this ;)

Notes

  • Does not support user based API credentials, only Environment API credentials (one env, no cluster support)

  • Does not support images other than docker formatted images. docker: prefix is forced!

    • Images follow a standardized format. See deploy_container docstring!
  • Does not support filters for searching/listing. (no image or all support in list_containers)

  • The launchConfig for ex_deploy_service is actually all defined at the top level. This is because I easily pass it through _build_payload (**kwargs) since the options are nearly identical to those of a normal container. No reason to duplicate work. This also makes it easier since most people only generally want to deploy a service with only one container anyways. This may need to change later when we want >1 container for a service.

  • Passing your own cert/key for SSL/TLS is not presently supported.

  • For SSL/TLS (https) support with newer versions of OpenSSL, the following is necessary (in your own code):

    import ssl
    import libcloud.security
    libcloud.security.SSL_VERSION = ssl.PROTOCOL_TLSv1_2
    

Checklist (tick everything that applies)

  • Code linting (required, can be done after the PR checks) - flake8 completed successfully ;)
  • Documentation - See Docstrings and docs/
  • Tests - Perfected ;)
  • ICLA (required for bigger changes) - Corporate Agreement Submitted!

Thanks

I have worked closely with Anthony Shaw, a core contributor to Libcloud (especially the Container stuffs) as well as Vincent Fiduccia, software architect at Rancher Labs. The have been invaluable in helping me establish a solid driver for Rancher.

@tonybaloney

This comment has been minimized.

Show comment
Hide comment
@tonybaloney

tonybaloney Sep 29, 2016

Contributor

this is fantastic work, I'll work through the review

Contributor

tonybaloney commented Sep 29, 2016

this is fantastic work, I'll work through the review

@morissette

This comment has been minimized.

Show comment
Hide comment
@morissette

morissette Sep 29, 2016

Love it! Is their a direct kubernetes driver yet? Im on the road else id
look at source

On Thursday, September 29, 2016, Anthony Shaw notifications@github.com
wrote:

this is fantastic work, I'll work through the review


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#876 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ADdgZ4e87yOMzidAuP8ryItrd7wQ7tDmks5qu1MggaJpZM4KJi43
.

morissette commented Sep 29, 2016

Love it! Is their a direct kubernetes driver yet? Im on the road else id
look at source

On Thursday, September 29, 2016, Anthony Shaw notifications@github.com
wrote:

this is fantastic work, I'll work through the review


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#876 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ADdgZ4e87yOMzidAuP8ryItrd7wQ7tDmks5qu1MggaJpZM4KJi43
.

@vincent99

This comment has been minimized.

Show comment
Hide comment
@vincent99

vincent99 Sep 29, 2016

Nice @InAnimaTe

Random comments related to the Rancher integration...

  • A little late now, but we do have a python API binding you could use.
  • Listing every field of resources like container/service is going to quickly be out of date.. I don't know if passing a map is an option that fits into the rest of libcloud rather than 30 positional params, but if so you can save a lot of work keeping it in sync.
  • resource.transitioning == 'yes' would be better for PENDING state than endswith('ing')
  • API v2[-beta] renames environment to stack, so while you might not want to use v2 yet (for compatibility with Rancher v1.1.x) you should probably refer to it as a stack in the public interface.
  • outputs, previousEnvironment & previousExternalId are technically settable by users for obscure technical reasons, but are not really user inputs. So you could remove those as options for a env/stack.

vincent99 commented Sep 29, 2016

Nice @InAnimaTe

Random comments related to the Rancher integration...

  • A little late now, but we do have a python API binding you could use.
  • Listing every field of resources like container/service is going to quickly be out of date.. I don't know if passing a map is an option that fits into the rest of libcloud rather than 30 positional params, but if so you can save a lot of work keeping it in sync.
  • resource.transitioning == 'yes' would be better for PENDING state than endswith('ing')
  • API v2[-beta] renames environment to stack, so while you might not want to use v2 yet (for compatibility with Rancher v1.1.x) you should probably refer to it as a stack in the public interface.
  • outputs, previousEnvironment & previousExternalId are technically settable by users for obscure technical reasons, but are not really user inputs. So you could remove those as options for a env/stack.
@seglberg

This comment has been minimized.

Show comment
Hide comment
@seglberg

seglberg Sep 29, 2016

Contributor

I agree, having all those arguments is outrageous and will fail quickly. No reason why the common ones can't be called out directly and the rest of them just accepted as kwargs.

Contributor

seglberg commented Sep 29, 2016

I agree, having all those arguments is outrageous and will fail quickly. No reason why the common ones can't be called out directly and the rest of them just accepted as kwargs.

@seglberg

Looking good.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
class RancherResponse(JsonResponse):
def parse_body(self):

This comment has been minimized.

@seglberg

seglberg Sep 29, 2016

Contributor

Why override this method? Two issues:

  1. Your version just returns None if the content-type header is not set by the server (oops).
  2. Below you set the Accept header to application/json only; so it would be reasonable that we should only get json back.

I think you should just let the base class handle this.

@seglberg

seglberg Sep 29, 2016

Contributor

Why override this method? Two issues:

  1. Your version just returns None if the content-type header is not set by the server (oops).
  2. Below you set the Accept header to application/json only; so it would be reasonable that we should only get json back.

I think you should just let the base class handle this.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
def parse_error(self):
if self.status == 401:
raise InvalidCredsError('Invalid credentials')
return self.body

This comment has been minimized.

@seglberg

seglberg Sep 29, 2016

Contributor

Should super be called here?

@seglberg

seglberg Sep 29, 2016

Contributor

Should super be called here?

This comment has been minimized.

@seglberg

seglberg Oct 6, 2016

Contributor

This still needs to be addressed.

@seglberg

seglberg Oct 6, 2016

Contributor

This still needs to be addressed.

This comment has been minimized.

@tonybaloney

tonybaloney Oct 7, 2016

Contributor

you don't need to call super. the super has no constructor and its only if you wanted to pass specified args to it which you aren't collecting. If rancher APIs have a special error model then this is the bit where you abstract that field from the body and raise the correct error.

@tonybaloney

tonybaloney Oct 7, 2016

Contributor

you don't need to call super. the super has no constructor and its only if you wanted to pass specified args to it which you aren't collecting. If rancher APIs have a special error model then this is the bit where you abstract that field from the body and raise the correct error.

This comment has been minimized.

@seglberg

seglberg Oct 7, 2016

Contributor

I'm not talking about adding an init to this class, I mean that it should be return super().parse_error(). because looking at the base class, it looks like parse_error is suppose to return a parsed response, not just the raw response body.

@seglberg

seglberg Oct 7, 2016

Contributor

I'm not talking about adding an init to this class, I mean that it should be return super().parse_error(). because looking at the base class, it looks like parse_error is suppose to return a parsed response, not just the raw response body.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
# As in the /v1/
version = '1'
def __init__(self, key, secret, secure=False, host='localhost', port=80):

This comment has been minimized.

@seglberg

seglberg Sep 29, 2016

Contributor

Why not secure by default like the base classes?

@seglberg

seglberg Sep 29, 2016

Contributor

Why not secure by default like the base classes?

Show outdated Hide outdated libcloud/container/drivers/rancher.py
self.connection.host = host
self.connection.port = port
self.connection.secure = secure

This comment has been minimized.

@seglberg

seglberg Sep 29, 2016

Contributor

Why are these three lines necessary if the base class is suppose to initialize the connection? Seems redundant.

@seglberg

seglberg Sep 29, 2016

Contributor

Why are these three lines necessary if the base class is suppose to initialize the connection? Seems redundant.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
secure=secure, host=host,
port=port)
if host.startswith('https://'):
secure = True

This comment has been minimized.

@seglberg

seglberg Sep 29, 2016

Contributor

See my other comment above, but I would think it should default to true, and then be disabled (to follow the pattern of the base classes).

Also, shouldn't this be set before the call to super's init? Otherwise its not caught in the base classes.

@seglberg

seglberg Sep 29, 2016

Contributor

See my other comment above, but I would think it should default to true, and then be disabled (to follow the pattern of the base classes).

Also, shouldn't this be set before the call to super's init? Otherwise its not caught in the base classes.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
"rancherCompose": ranchercompose,
"startOnCreate": start
}
data = json.dumps({k: v for k, v in payload.items() if v is not None})

This comment has been minimized.

@seglberg

seglberg Sep 29, 2016

Contributor

Why not just let the method accept **payload keyword args?

@seglberg

seglberg Sep 29, 2016

Contributor

Why not just let the method accept **payload keyword args?

Show outdated Hide outdated libcloud/container/drivers/rancher.py
disks=None, kind=None, memorymb=None,
networklaunchconfig=None, requsetedipaddress=None,
userdata=None, vcpu=None, **kwargs):
"""

This comment has been minimized.

@seglberg

seglberg Sep 29, 2016

Contributor

Not to nitpick too much, but if we're passing around so many damn arguments, then at least use shorter and pythonic naming conventions to help ease with reading the code, e.g. networklaunchconfig -> net_launch_conf

I think it would be better to accept a dict of service arguments, e.g. (self, name, image, env_id, service_args=None, **payload)

@seglberg

seglberg Sep 29, 2016

Contributor

Not to nitpick too much, but if we're passing around so many damn arguments, then at least use shorter and pythonic naming conventions to help ease with reading the code, e.g. networklaunchconfig -> net_launch_conf

I think it would be better to accept a dict of service arguments, e.g. (self, name, image, env_id, service_args=None, **payload)

This comment has been minimized.

@tonybaloney

tonybaloney Sep 30, 2016

Contributor

yep. snake_casing please

@tonybaloney

tonybaloney Sep 30, 2016

Contributor

yep. snake_casing please

@InAnimaTe

This comment has been minimized.

Show comment
Hide comment
@InAnimaTe

InAnimaTe Sep 30, 2016

Contributor

Thanks a lot of the input everyone. I've modified and changed around quite a bit based on input which has made this driver quite a bit better ;)

I'd like some more input from @tonybaloney about how the response class should be utilized. Other than that, I'm working on a test (similar to the docker test) and should have a license agreement signed soon.

Contributor

InAnimaTe commented Sep 30, 2016

Thanks a lot of the input everyone. I've modified and changed around quite a bit based on input which has made this driver quite a bit better ;)

I'd like some more input from @tonybaloney about how the response class should be utilized. Other than that, I'm working on a test (similar to the docker test) and should have a license agreement signed soon.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
launchconfig = {
"imageUuid": self._degen_image(image),
**kwargs

This comment has been minimized.

@tonybaloney

tonybaloney Oct 5, 2016

Contributor

You can't do that :-) if you want to merge the 2 you can just use the + operator. what's the intention here?

@tonybaloney

tonybaloney Oct 5, 2016

Contributor

You can't do that :-) if you want to merge the 2 you can just use the + operator. what's the intention here?

This comment has been minimized.

@InAnimaTe

InAnimaTe Oct 5, 2016

Contributor

So, it does actually work (something new with python3 i guess). What I'm trying to do there is take in extra container options and adding them to the launchConfig. This makes it super easy to use that function and for other programs that utilize this (they don't have to worry about building a separate dict if they just want to modify a few options)

@InAnimaTe

InAnimaTe Oct 5, 2016

Contributor

So, it does actually work (something new with python3 i guess). What I'm trying to do there is take in extra container options and adding them to the launchConfig. This makes it super easy to use that function and for other programs that utilize this (they don't have to worry about building a separate dict if they just want to modify a few options)

This comment has been minimized.

@InAnimaTe

InAnimaTe Oct 5, 2016

Contributor

Remembering that this has to work in Py2, I'll use the dict constructor and change it here ;)

@InAnimaTe

InAnimaTe Oct 5, 2016

Contributor

Remembering that this has to work in Py2, I'll use the dict constructor and change it here ;)

Show outdated Hide outdated libcloud/container/drivers/rancher.py
secure=secure, host=host,
port=port)
if host.startswith('http://'):
secure = False

This comment has been minimized.

@seglberg

seglberg Oct 6, 2016

Contributor

This should be called before the call to super, so the base classes get the change.

@seglberg

seglberg Oct 6, 2016

Contributor

This should be called before the call to super, so the base classes get the change.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
"vip": vip
}
data = json.dumps({k: v for k, v in service_payload.items()

This comment has been minimized.

@seglberg

seglberg Oct 6, 2016

Contributor

If libcloud is targeting < Py2.7 then dict comprehensions will not work. If so, just use dict(..)

@seglberg

seglberg Oct 6, 2016

Contributor

If libcloud is targeting < Py2.7 then dict comprehensions will not work. If so, just use dict(..)

This comment has been minimized.

@tonybaloney

tonybaloney Oct 6, 2016

Contributor

it does target 2.6

@tonybaloney

tonybaloney Oct 6, 2016

Contributor

it does target 2.6

Show outdated Hide outdated libcloud/container/drivers/rancher.py
def parse_error(self):
if self.status == 401:
raise InvalidCredsError('Invalid credentials')
return self.body

This comment has been minimized.

@seglberg

seglberg Oct 6, 2016

Contributor

This still needs to be addressed.

@seglberg

seglberg Oct 6, 2016

Contributor

This still needs to be addressed.

Show outdated Hide outdated libcloud/container/drivers/rancher.py
@@ -41,7 +41,7 @@ class RancherResponse(JsonResponse):
def parse_error(self):
if self.status == 401:
raise InvalidCredsError('Invalid credentials')
return self.body
return super().parse_error()

This comment has been minimized.

@tonybaloney

tonybaloney Oct 7, 2016

Contributor

You can just take this method out altogether and let it fall through to the base class if you're not going to do anything Rancher-specific with the response.

@tonybaloney

tonybaloney Oct 7, 2016

Contributor

You can just take this method out altogether and let it fall through to the base class if you're not going to do anything Rancher-specific with the response.

@tonybaloney

This comment has been minimized.

Show comment
Hide comment
@tonybaloney

tonybaloney Oct 7, 2016

Much better! That's what I meant

Much better! That's what I meant

@seglberg

This comment has been minimized.

Show comment
Hide comment
@seglberg

seglberg Oct 8, 2016

Contributor

👍

Contributor

seglberg commented Oct 8, 2016

👍

@tonybaloney

This comment has been minimized.

Show comment
Hide comment
@tonybaloney

tonybaloney Oct 8, 2016

Contributor

fantastic. congratulations! LGTM - merging.

Contributor

tonybaloney commented Oct 8, 2016

fantastic. congratulations! LGTM - merging.

@asfgit asfgit closed this in 5f1d6bc Oct 8, 2016

asfgit pushed a commit that referenced this pull request Oct 8, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment