Skip to content
This repository has been archived by the owner on Mar 13, 2022. It is now read-only.

Dynamic Client #56

Merged
merged 3 commits into from
Aug 12, 2019
Merged

Conversation

fabianvf
Copy link
Contributor

@fabianvf fabianvf commented Apr 9, 2018

Still TODO:

  • Support for subresources
  • Support for async
  • Delete options
  • Ability to easily retrigger discovery
  • Documentation
  • Tests

@codecov-io
Copy link

codecov-io commented Apr 9, 2018

Codecov Report

Merging #56 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master      #56   +/-   ##
=======================================
  Coverage   91.85%   91.85%           
=======================================
  Files          11       11           
  Lines         994      994           
=======================================
  Hits          913      913           
  Misses         81       81

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 24a0ff2...788d114. Read the comment docs.

@fabianvf
Copy link
Contributor Author

@mbohlool

@mbohlool
Copy link
Contributor

mbohlool commented May 4, 2018

@yliaog @roycaihw

@fabianvf fabianvf changed the title [WIP] Dynamic Client Dynamic Client May 4, 2018
Copy link
Member

@roycaihw roycaihw left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution! I left some comments :)

from kubernetes.client.api_client import ApiClient
from kubernetes.client.rest import ApiException

from openshift.dynamic.exceptions import ResourceNotFoundError, ResourceNotUniqueError, api_exception
Copy link
Member

Choose a reason for hiding this comment

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

The module path needs to be changed

def group_version(self):
if self.group:
return '{}/{}'.format(self.group, self.api_version)
return self.api_version
Copy link
Member

Choose a reason for hiding this comment

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

nit: '/'.join(filter(None, [self.group, self.api_version])) like Line112?

kind: The kind of the resource
arbitrary arguments (see below), in random order

The arbitrary arguments can be any valid attribute for an openshift.dynamic.Resource object
Copy link
Member

Choose a reason for hiding this comment

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

ditto: update the module path

def path(self, name=None, namespace=None):
url_type = []
path_params = {}
if self.namespaced and namespace:
Copy link
Member

Choose a reason for hiding this comment

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

should we catch the case where namespaced is set but namespace is None?

or does ensure_namespace rule out that case already?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if namespaced is set and namespace is None, then we can still construct a valid path (api/v1/pods for example)

'base': '/{}/{}'.format(full_prefix, self.name),
'namespaced_base': '/{}/namespaces/{{namespace}}/{}'.format(full_prefix, self.name),
'full': '/{}/{}/{{name}}'.format(full_prefix, self.name),
'namespaced_full': '/{}/namespaces/{{namespace}}/{}/{{name}}'.format(full_prefix, self.name)
Copy link
Member

Choose a reason for hiding this comment

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

can we determine if the resource is cluster-scoped or namespaced using self.namespaced here and only return two urls base and full?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you can access namespaced resources without providing a namespace (ie, list all pods in every namespace, etc), so I don't think we should

content_type = self.client.\
select_header_content_type(['application/json-patch+json', 'application/merge-patch+json', 'application/strategic-merge-patch+json'])

return self.request('patch', path, body=body, content_type=content_type)
Copy link
Member

Choose a reason for hiding this comment

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

pass **kwargs to self.request

dynamic/client.py Show resolved Hide resolved
@meta_request
def delete(self, resource, name=None, namespace=None, label_selector=None, field_selector=None, **kwargs):
if not (name or label_selector or field_selector):
raise ValueError("At least one of name|label_selector|field_selector is required")
Copy link
Member

Choose a reason for hiding this comment

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

From the design doc, the following actions are marked as TODO: update, watch, deletecollection, proxy. I see update has been implemented (replace). I'm just curious if we don't want to have deletecollection for the first iteration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the only reason I didn't yet implement deletecollection is that I'm not familiar with it, would love to get it in

Copy link
Contributor Author

Choose a reason for hiding this comment

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

still TODO

Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine to add deletecollection as a followup

full_prefix = '{}/{}'.format(self.prefix, self.group_version)
return {
'full': '/{}/{}/{{name}}/{}'.format(full_prefix, self.parent.name, self.subresource),
'namespaced_full': '/{}/namespaces/{{namespace}}/{}/{{name}}/{}'.format(full_prefix, self.parent.name, self.subresource)
Copy link
Member

Choose a reason for hiding this comment

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

ditto: if we could check self.namespaced and combine the two urls into one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see above response

def _load_server_info(self):
self.__version = {'kubernetes': load_json(self.request('get', '/version'))}
try:
self.__version['openshift'] = load_json(self.request('get', '/version/openshift'))
Copy link
Member

Choose a reason for hiding this comment

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

I suppose this is not specific to kubernetes cluster api discovery. Not sure how we want organize the code. Looping in @yliaog @mbohlool

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can go ahead and take it out, this belongs in openshift specific code not upstream

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jul 30, 2018
@fabianvf
Copy link
Contributor Author

@roycaihw rebased to keep up to date with branch, any additional comments?

@fabianvf
Copy link
Contributor Author

fabianvf commented Aug 8, 2018

ping @roycaihw

@fabianvf
Copy link
Contributor Author

fabianvf commented Aug 8, 2018

We've also had some community interest in the other discussed dynamic implementation (the one that uses the OpenAPI spec for discovery), I haven't had time to look into it again yet but I'm hoping I'll get a chance for a second pass soon.

Copy link
Member

@roycaihw roycaihw left a comment

Choose a reason for hiding this comment

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

It looks good to me in general. I left one nit and one question for clarification.

return self.request('post', path, body=body, **kwargs)

@meta_request
def delete(self, resource, name=None, namespace=None, label_selector=None, field_selector=None, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

k8s delete requests can have body of type DeleteOptions as alternative of some query parameters, although it's not a good practice to send delete request with body I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, this should definitely support delete 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.

still TODO

self.client = client
self.configuration = client.configuration
self._load_server_info()
self.__resources = ResourceContainer(self.parse_api_groups())
Copy link
Member

Choose a reason for hiding this comment

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

IIUC people need to create another ResourceContainer with client.parse_api_groups() when they want to re-discover new CRDs and aggregated APIs - self.__resources only records the resources that exist when the dynamic client gets created - is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, I'll break this stuff out into a discovery() method so that it can be called repeatedly

Copy link
Member

Choose a reason for hiding this comment

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

I'll break this stuff out into a discovery() method so that it can be called repeatedly

This can be done in a separate PR.

@roycaihw
Copy link
Member

I think this PR will be good to go after @fabianvf adds body to delete requests. @yliaog @mbohlool Any other comments?

This dynamic client should also be e2e tested (something like test_client.py), probably in another PR.

from functools import partial
import yaml
from pprint import pformat
from kubernetes.client.rest import ApiException
Copy link
Contributor

Choose a reason for hiding this comment

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

better to have the import above be ordered?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I can clean that up

503: ServiceUnavailableError,
504: ServerTimeoutError,
}.get(e.status, DynamicApiError)(e, tb)

Copy link
Contributor

Choose a reason for hiding this comment

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

if just want to add traceback, why do you need to add all these specific HTTP errors (BadRequestError, UnauthorizedError, ...)? Is DynamicApiError alone not enough for the purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer to expose errors that correspond to the HTTP errors we receive, it provides a cleaner way to handle errors when consuming the client (since you can just catch a NotFoundError rather than catching a DynamicApiError and checking the status code). Catching a generic DynamicApiError and checking the code still works if the user prefers to handle it that way.

header_params['Accept'] = self.client.select_header_accept([
'application/json',
'application/yaml',
'application/vnd.kubernetes.protobuf'
Copy link
Contributor

Choose a reason for hiding this comment

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

i don't think protobuf is supported in python client, better to remove it 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.

ack, good call

@fabianvf
Copy link
Contributor Author

fabianvf commented Sep 5, 2018

sorry, I've been on PTO + planning meetings the last few weeks, I'll try and carve up some time to clean all this up sometime this week or next

@fabianvf
Copy link
Contributor Author

We're actually doing a little bit of a rework of the dynamic client in openshift, driven by user feedback. I think I might hold off a bit on updating this PR, so that I can bring those improvements in

@micw523
Copy link
Contributor

micw523 commented Mar 19, 2019

@fabianvf I see that you're working on a dynamic client for OpenShift. Are there any plans to backport the dynamic client to this library?

@fabianvf
Copy link
Contributor Author

Yes, there's actually almost nothing specific to openshift in the client. There was a lot of churn so I put this PR on hold while we stabilized a bit, now it's mostly a matter of finding the time to bring in the relevant new stuff.

@fejta-bot
Copy link

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle stale

@k8s-ci-robot k8s-ci-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Jun 17, 2019
@micw523
Copy link
Contributor

micw523 commented Jun 17, 2019 via email

@k8s-ci-robot k8s-ci-robot removed the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Jun 17, 2019
@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jun 28, 2019
@roycaihw
Copy link
Member

roycaihw commented Aug 6, 2019

/lgtm
/approve

Thanks a lot for working on this! Will merge after the test failure gets resolved kubernetes-client/python#919

@k8s-ci-robot k8s-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Aug 6, 2019
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: fabianvf, roycaihw

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 6, 2019
@yliaog
Copy link
Contributor

yliaog commented Aug 7, 2019

i have a meta request, could you please squash the commits to a few logical commits?

@k8s-ci-robot k8s-ci-robot removed the lgtm Indicates that a PR is ready to be merged. label Aug 10, 2019
@roycaihw
Copy link
Member

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Aug 12, 2019
@k8s-ci-robot k8s-ci-robot merged commit 4b8e89f into kubernetes-client:master Aug 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm Indicates that a PR is ready to be merged. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants