Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

New Rancher Container Driver #876

Closed
wants to merge 25 commits into from
Closed

New Rancher Container Driver #876

wants to merge 25 commits into from

Conversation

InAnimaTe
Copy link
Contributor

@InAnimaTe 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
Copy link
Contributor

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

@morissette
Copy link

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
Copy link

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.

@mmellison
Copy link
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.

Copy link
Contributor

@mmellison mmellison left a comment

Choose a reason for hiding this comment

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

Looking good.


class RancherResponse(JsonResponse):

def parse_body(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

def parse_error(self):
if self.status == 401:
raise InvalidCredsError('Invalid credentials')
return self.body
Copy link
Contributor

Choose a reason for hiding this comment

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

Should super be called here?

Copy link
Contributor

Choose a reason for hiding this comment

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

This still needs to be addressed.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

# As in the /v1/
version = '1'

def __init__(self, key, secret, secure=False, host='localhost', port=80):
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not secure by default like the base classes?

self.connection.host = host
self.connection.port = port
self.connection.secure = secure

Copy link
Contributor

Choose a reason for hiding this comment

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

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

secure=secure, host=host,
port=port)
if host.startswith('https://'):
secure = True
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

"rancherCompose": ranchercompose,
"startOnCreate": start
}
data = json.dumps({k: v for k, v in payload.items() if v is not None})
Copy link
Contributor

Choose a reason for hiding this comment

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

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

disks=None, kind=None, memorymb=None,
networklaunchconfig=None, requsetedipaddress=None,
userdata=None, vcpu=None, **kwargs):
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Contributor

Choose a reason for hiding this comment

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

yep. snake_casing please

@InAnimaTe
Copy link
Contributor Author

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.


launchconfig = {
"imageUuid": self._degen_image(image),
**kwargs
Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

secure=secure, host=host,
port=port)
if host.startswith('http://'):
secure = False
Copy link
Contributor

Choose a reason for hiding this comment

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

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

"vip": vip
}

data = json.dumps({k: v for k, v in service_payload.items()
Copy link
Contributor

@mmellison mmellison Oct 6, 2016

Choose a reason for hiding this comment

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

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

Copy link
Contributor

Choose a reason for hiding this comment

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

it does target 2.6

def parse_error(self):
if self.status == 401:
raise InvalidCredsError('Invalid credentials')
return self.body
Copy link
Contributor

Choose a reason for hiding this comment

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

This still needs to be addressed.

@@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

@mmellison
Copy link
Contributor

👍

@tonybaloney
Copy link
Contributor

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
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants