Merge "Add client resources and managers"
This commit is contained in:
commit
6203b18de7
99
magnumclient/api/bays.py
Normal file
99
magnumclient/api/bays.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from magnumclient.common import base
|
||||||
|
from magnumclient.common import utils
|
||||||
|
from magnumclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: Modify correct attributes.
|
||||||
|
CREATION_ATTRIBUTES = ['description']
|
||||||
|
|
||||||
|
|
||||||
|
class Bay(base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Bay %s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class BayManager(base.Manager):
|
||||||
|
resource_class = Bay
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _path(id=None):
|
||||||
|
return '/v1/bays/%s' % id if id else '/v1/bays'
|
||||||
|
|
||||||
|
def list(self, marker=None, limit=None, sort_key=None,
|
||||||
|
sort_dir=None, detail=False):
|
||||||
|
"""Retrieve a list of bays.
|
||||||
|
|
||||||
|
:param marker: Optional, the UUID of a bays, eg the last
|
||||||
|
bays from a previous result set. Return
|
||||||
|
the next result set.
|
||||||
|
:param limit: The maximum number of results to return per
|
||||||
|
request, if:
|
||||||
|
|
||||||
|
1) limit > 0, the maximum number of bays to return.
|
||||||
|
2) limit == 0, return the entire list of bays.
|
||||||
|
3) limit param is NOT specified (None), the number of items
|
||||||
|
returned respect the maximum imposed by the Magnum API
|
||||||
|
(see Magnum's api.max_limit option).
|
||||||
|
|
||||||
|
:param sort_key: Optional, field used for sorting.
|
||||||
|
|
||||||
|
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||||
|
default) or 'desc'.
|
||||||
|
|
||||||
|
:param detail: Optional, boolean whether to return detailed information
|
||||||
|
about bays.
|
||||||
|
|
||||||
|
:returns: A list of bays.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if limit is not None:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
path = ''
|
||||||
|
if detail:
|
||||||
|
path += 'detail'
|
||||||
|
if filters:
|
||||||
|
path += '?' + '&'.join(filters)
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
return self._list(self._path(path), "bays")
|
||||||
|
else:
|
||||||
|
return self._list_pagination(self._path(path), "bays",
|
||||||
|
limit=limit)
|
||||||
|
|
||||||
|
def get(self, bay_id):
|
||||||
|
try:
|
||||||
|
return self._list(self._path(bay_id))[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
new = {}
|
||||||
|
for (key, value) in kwargs.items():
|
||||||
|
if key in CREATION_ATTRIBUTES:
|
||||||
|
new[key] = value
|
||||||
|
else:
|
||||||
|
raise exceptions.InvalidAttribute()
|
||||||
|
return self._create(self._path(), new)
|
||||||
|
|
||||||
|
def delete(self, bay_id):
|
||||||
|
return self._delete(self._path(bay_id))
|
||||||
|
|
||||||
|
def update(self, bay_id, patch):
|
||||||
|
return self._update(self._path(bay_id), patch)
|
@ -16,7 +16,11 @@
|
|||||||
from keystoneclient.v2_0 import client as keystone_client_v2
|
from keystoneclient.v2_0 import client as keystone_client_v2
|
||||||
from keystoneclient.v3 import client as keystone_client_v3
|
from keystoneclient.v3 import client as keystone_client_v3
|
||||||
|
|
||||||
from magnumclient.api import httpclient
|
from magnumclient.api import bays
|
||||||
|
from magnumclient.api import containers
|
||||||
|
from magnumclient.api import pods
|
||||||
|
from magnumclient.api import services
|
||||||
|
from magnumclient.common import httpclient
|
||||||
|
|
||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
@ -52,8 +56,26 @@ class Client(object):
|
|||||||
if not magnum_catalog_url:
|
if not magnum_catalog_url:
|
||||||
raise RuntimeError("Could not find Magnum endpoint in catalog")
|
raise RuntimeError("Could not find Magnum endpoint in catalog")
|
||||||
|
|
||||||
self.client = httpclient.HTTPClient(magnum_catalog_url,
|
http_cli_kwargs = {
|
||||||
input_auth_token)
|
'token': input_auth_token,
|
||||||
|
# TODO(yuanying): - use insecure
|
||||||
|
# 'insecure': kwargs.get('insecure'),
|
||||||
|
# TODO(yuanying): - use timeout
|
||||||
|
# 'timeout': kwargs.get('timeout'),
|
||||||
|
# TODO(yuanying): - use ca_file
|
||||||
|
# 'ca_file': kwargs.get('ca_file'),
|
||||||
|
# TODO(yuanying): - use cert_file
|
||||||
|
# 'cert_file': kwargs.get('cert_file'),
|
||||||
|
# TODO(yuanying): - use key_file
|
||||||
|
# 'key_file': kwargs.get('key_file'),
|
||||||
|
'auth_ref': None,
|
||||||
|
}
|
||||||
|
self.http_client = httpclient.HTTPClient(magnum_catalog_url,
|
||||||
|
**http_cli_kwargs)
|
||||||
|
self.bays = bays.BayManager(self.http_client)
|
||||||
|
self.pods = pods.PodManager(self.http_client)
|
||||||
|
self.services = services.ServiceManager(self.http_client)
|
||||||
|
self.containers = containers.ContainerManager(self.http_client)
|
||||||
|
|
||||||
def get_keystone_client(self, username=None, api_key=None, auth_url=None,
|
def get_keystone_client(self, username=None, api_key=None, auth_url=None,
|
||||||
token=None, project_id=None, project_name=None):
|
token=None, project_id=None, project_name=None):
|
||||||
|
99
magnumclient/api/containers.py
Normal file
99
magnumclient/api/containers.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from magnumclient.common import base
|
||||||
|
from magnumclient.common import utils
|
||||||
|
from magnumclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: Modify correct attributes.
|
||||||
|
CREATION_ATTRIBUTES = ['description']
|
||||||
|
|
||||||
|
|
||||||
|
class Container(base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Container %s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerManager(base.Manager):
|
||||||
|
resource_class = Container
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _path(id=None):
|
||||||
|
return '/v1/containers/%s' % id if id else '/v1/containers'
|
||||||
|
|
||||||
|
def list(self, marker=None, limit=None, sort_key=None,
|
||||||
|
sort_dir=None, detail=False):
|
||||||
|
"""Retrieve a list of containers.
|
||||||
|
|
||||||
|
:param marker: Optional, the UUID of a containers, eg the last
|
||||||
|
containers from a previous result set. Return
|
||||||
|
the next result set.
|
||||||
|
:param limit: The maximum number of results to return per
|
||||||
|
request, if:
|
||||||
|
|
||||||
|
1) limit > 0, the maximum number of containers to return.
|
||||||
|
2) limit == 0, return the entire list of containers.
|
||||||
|
3) limit param is NOT specified (None), the number of items
|
||||||
|
returned respect the maximum imposed by the Magnum API
|
||||||
|
(see Magnum's api.max_limit option).
|
||||||
|
|
||||||
|
:param sort_key: Optional, field used for sorting.
|
||||||
|
|
||||||
|
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||||
|
default) or 'desc'.
|
||||||
|
|
||||||
|
:param detail: Optional, boolean whether to return detailed information
|
||||||
|
about containers.
|
||||||
|
|
||||||
|
:returns: A list of containers.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if limit is not None:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
path = ''
|
||||||
|
if detail:
|
||||||
|
path += 'detail'
|
||||||
|
if filters:
|
||||||
|
path += '?' + '&'.join(filters)
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
return self._list(self._path(path), "containers")
|
||||||
|
else:
|
||||||
|
return self._list_pagination(self._path(path), "containers",
|
||||||
|
limit=limit)
|
||||||
|
|
||||||
|
def get(self, container_id):
|
||||||
|
try:
|
||||||
|
return self._list(self._path(container_id))[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
new = {}
|
||||||
|
for (key, value) in kwargs.items():
|
||||||
|
if key in CREATION_ATTRIBUTES:
|
||||||
|
new[key] = value
|
||||||
|
else:
|
||||||
|
raise exceptions.InvalidAttribute()
|
||||||
|
return self._create(self._path(), new)
|
||||||
|
|
||||||
|
def delete(self, container_id):
|
||||||
|
return self._delete(self._path(container_id))
|
||||||
|
|
||||||
|
def update(self, container_id, patch):
|
||||||
|
return self._update(self._path(container_id), patch)
|
@ -1,42 +0,0 @@
|
|||||||
# Copyright 2014
|
|
||||||
# The Cloudscaling Group, Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
# use this file except in compliance with the License. You may obtain a copy
|
|
||||||
# of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient(object):
|
|
||||||
def __init__(self, base_url, token):
|
|
||||||
self.base_url = base_url
|
|
||||||
self.token = token
|
|
||||||
|
|
||||||
def get(self, url):
|
|
||||||
return requests.get(self.base_url + url,
|
|
||||||
headers={'x-auth-token': self.token})
|
|
||||||
|
|
||||||
def post(self, url, body, json=True):
|
|
||||||
headers = {'x-auth-token': self.token}
|
|
||||||
if json:
|
|
||||||
headers['content-type'] = 'application/json'
|
|
||||||
return requests.post(self.base_url + url, body, headers=headers)
|
|
||||||
|
|
||||||
def put(self, url, body, json=True):
|
|
||||||
headers = {'x-auth-token': self.token}
|
|
||||||
if json:
|
|
||||||
headers['content-type'] = 'application/json'
|
|
||||||
return requests.put(self.base_url + url, body, headers=headers)
|
|
||||||
|
|
||||||
def delete(self, url):
|
|
||||||
return requests.delete(self.base_url + url,
|
|
||||||
headers={'x-auth-token': self.token})
|
|
99
magnumclient/api/pods.py
Normal file
99
magnumclient/api/pods.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from magnumclient.common import base
|
||||||
|
from magnumclient.common import utils
|
||||||
|
from magnumclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: Modify correct attributes.
|
||||||
|
CREATION_ATTRIBUTES = ['description']
|
||||||
|
|
||||||
|
|
||||||
|
class Pod(base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Pod %s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class PodManager(base.Manager):
|
||||||
|
resource_class = Pod
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _path(id=None):
|
||||||
|
return '/v1/pods/%s' % id if id else '/v1/pods'
|
||||||
|
|
||||||
|
def list(self, marker=None, limit=None, sort_key=None,
|
||||||
|
sort_dir=None, detail=False):
|
||||||
|
"""Retrieve a list of pods.
|
||||||
|
|
||||||
|
:param marker: Optional, the UUID of a pods, eg the last
|
||||||
|
pods from a previous result set. Return
|
||||||
|
the next result set.
|
||||||
|
:param limit: The maximum number of results to return per
|
||||||
|
request, if:
|
||||||
|
|
||||||
|
1) limit > 0, the maximum number of pods to return.
|
||||||
|
2) limit == 0, return the entire list of pods.
|
||||||
|
3) limit param is NOT specified (None), the number of items
|
||||||
|
returned respect the maximum imposed by the Magnum API
|
||||||
|
(see Magnum's api.max_limit option).
|
||||||
|
|
||||||
|
:param sort_key: Optional, field used for sorting.
|
||||||
|
|
||||||
|
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||||
|
default) or 'desc'.
|
||||||
|
|
||||||
|
:param detail: Optional, boolean whether to return detailed information
|
||||||
|
about pods.
|
||||||
|
|
||||||
|
:returns: A list of pods.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if limit is not None:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
path = ''
|
||||||
|
if detail:
|
||||||
|
path += 'detail'
|
||||||
|
if filters:
|
||||||
|
path += '?' + '&'.join(filters)
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
return self._list(self._path(path), "pods")
|
||||||
|
else:
|
||||||
|
return self._list_pagination(self._path(path), "pods",
|
||||||
|
limit=limit)
|
||||||
|
|
||||||
|
def get(self, pod_id):
|
||||||
|
try:
|
||||||
|
return self._list(self._path(pod_id))[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
new = {}
|
||||||
|
for (key, value) in kwargs.items():
|
||||||
|
if key in CREATION_ATTRIBUTES:
|
||||||
|
new[key] = value
|
||||||
|
else:
|
||||||
|
raise exceptions.InvalidAttribute()
|
||||||
|
return self._create(self._path(), new)
|
||||||
|
|
||||||
|
def delete(self, pod_id):
|
||||||
|
return self._delete(self._path(pod_id))
|
||||||
|
|
||||||
|
def update(self, pod_id, patch):
|
||||||
|
return self._update(self._path(pod_id), patch)
|
98
magnumclient/api/services.py
Normal file
98
magnumclient/api/services.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from magnumclient.common import base
|
||||||
|
from magnumclient.common import utils
|
||||||
|
from magnumclient import exceptions
|
||||||
|
|
||||||
|
# FIXME: Modify correct attributes.
|
||||||
|
CREATION_ATTRIBUTES = ['description']
|
||||||
|
|
||||||
|
|
||||||
|
class Service(base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Service %s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceManager(base.Manager):
|
||||||
|
resource_class = Service
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _path(id=None):
|
||||||
|
return '/v1/services/%s' % id if id else '/v1/services'
|
||||||
|
|
||||||
|
def list(self, marker=None, limit=None, sort_key=None,
|
||||||
|
sort_dir=None, detail=False):
|
||||||
|
"""Retrieve a list of services.
|
||||||
|
|
||||||
|
:param marker: Optional, the UUID of a services, eg the last
|
||||||
|
services from a previous result set. Return
|
||||||
|
the next result set.
|
||||||
|
:param limit: The maximum number of results to return per
|
||||||
|
request, if:
|
||||||
|
|
||||||
|
1) limit > 0, the maximum number of services to return.
|
||||||
|
2) limit == 0, return the entire list of services.
|
||||||
|
3) limit param is NOT specified (None), the number of items
|
||||||
|
returned respect the maximum imposed by the Magnum API
|
||||||
|
(see Magnum's api.max_limit option).
|
||||||
|
|
||||||
|
:param sort_key: Optional, field used for sorting.
|
||||||
|
|
||||||
|
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||||
|
default) or 'desc'.
|
||||||
|
|
||||||
|
:param detail: Optional, boolean whether to return detailed information
|
||||||
|
about services.
|
||||||
|
|
||||||
|
:returns: A list of services.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if limit is not None:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
path = ''
|
||||||
|
if detail:
|
||||||
|
path += 'detail'
|
||||||
|
if filters:
|
||||||
|
path += '?' + '&'.join(filters)
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
return self._list(self._path(path), "services")
|
||||||
|
else:
|
||||||
|
return self._list_pagination(self._path(path), "services",
|
||||||
|
limit=limit)
|
||||||
|
|
||||||
|
def get(self, service_id):
|
||||||
|
try:
|
||||||
|
return self._list(self._path(service_id))[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
new = {}
|
||||||
|
for (key, value) in kwargs.items():
|
||||||
|
if key in CREATION_ATTRIBUTES:
|
||||||
|
new[key] = value
|
||||||
|
else:
|
||||||
|
raise exceptions.InvalidAttribute()
|
||||||
|
return self._create(self._path(), new)
|
||||||
|
|
||||||
|
def delete(self, service_id):
|
||||||
|
return self._delete(self._path(service_id))
|
||||||
|
|
||||||
|
def update(self, service_id, patch):
|
||||||
|
return self._update(self._path(service_id), patch)
|
0
magnumclient/common/__init__.py
Normal file
0
magnumclient/common/__init__.py
Normal file
146
magnumclient/common/base.py
Normal file
146
magnumclient/common/base.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2012 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base utilities to build API operation managers and objects on top of.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
|
from magnumclient.openstack.common.apiclient import base
|
||||||
|
|
||||||
|
|
||||||
|
def getid(obj):
|
||||||
|
"""Wrapper to get object's ID.
|
||||||
|
|
||||||
|
Abstracts the common pattern of allowing both an object or an
|
||||||
|
object's ID (UUID) as a parameter when dealing with relationships.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return obj.id
|
||||||
|
except AttributeError:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class Manager(object):
|
||||||
|
"""Provides CRUD operations with a particular API."""
|
||||||
|
resource_class = None
|
||||||
|
|
||||||
|
def __init__(self, api):
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
def _create(self, url, body):
|
||||||
|
resp, body = self.api.json_request('POST', url, body=body)
|
||||||
|
if body:
|
||||||
|
return self.resource_class(self, body)
|
||||||
|
|
||||||
|
def _format_body_data(self, body, response_key):
|
||||||
|
if response_key:
|
||||||
|
try:
|
||||||
|
data = body[response_key]
|
||||||
|
except KeyError:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
data = body
|
||||||
|
|
||||||
|
if not isinstance(data, list):
|
||||||
|
data = [data]
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _list_pagination(self, url, response_key=None, obj_class=None,
|
||||||
|
limit=None):
|
||||||
|
"""Retrieve a list of items.
|
||||||
|
|
||||||
|
The Magnum API is configured to return a maximum number of
|
||||||
|
items per request, (FIXME: see Magnum's api.max_limit option). This
|
||||||
|
iterates over the 'next' link (pagination) in the responses,
|
||||||
|
to get the number of items specified by 'limit'. If 'limit'
|
||||||
|
is None this function will continue pagination until there are
|
||||||
|
no more values to be returned.
|
||||||
|
|
||||||
|
:param url: a partial URL, e.g. '/nodes'
|
||||||
|
:param response_key: the key to be looked up in response
|
||||||
|
dictionary, e.g. 'nodes'
|
||||||
|
:param obj_class: class for constructing the returned objects.
|
||||||
|
:param limit: maximum number of items to return. If None returns
|
||||||
|
everything.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if obj_class is None:
|
||||||
|
obj_class = self.resource_class
|
||||||
|
|
||||||
|
if limit is not None:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
object_list = []
|
||||||
|
object_count = 0
|
||||||
|
limit_reached = False
|
||||||
|
while url:
|
||||||
|
resp, body = self.api.json_request('GET', url)
|
||||||
|
data = self._format_body_data(body, response_key)
|
||||||
|
for obj in data:
|
||||||
|
object_list.append(obj_class(self, obj, loaded=True))
|
||||||
|
object_count += 1
|
||||||
|
if limit and object_count >= limit:
|
||||||
|
# break the for loop
|
||||||
|
limit_reached = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# break the while loop and return
|
||||||
|
if limit_reached:
|
||||||
|
break
|
||||||
|
|
||||||
|
url = body.get('next')
|
||||||
|
if url:
|
||||||
|
# NOTE(lucasagomes): We need to edit the URL to remove
|
||||||
|
# the scheme and netloc
|
||||||
|
url_parts = list(urlparse.urlparse(url))
|
||||||
|
url_parts[0] = url_parts[1] = ''
|
||||||
|
url = urlparse.urlunparse(url_parts)
|
||||||
|
|
||||||
|
return object_list
|
||||||
|
|
||||||
|
def _list(self, url, response_key=None, obj_class=None, body=None):
|
||||||
|
resp, body = self.api.json_request('GET', url)
|
||||||
|
|
||||||
|
if obj_class is None:
|
||||||
|
obj_class = self.resource_class
|
||||||
|
|
||||||
|
data = self._format_body_data(body, response_key)
|
||||||
|
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||||
|
|
||||||
|
def _update(self, url, body, method='PATCH', response_key=None):
|
||||||
|
resp, body = self.api.json_request(method, url, body=body)
|
||||||
|
# PATCH/PUT requests may not return a body
|
||||||
|
if body:
|
||||||
|
return self.resource_class(self, body)
|
||||||
|
|
||||||
|
def _delete(self, url):
|
||||||
|
self.api.raw_request('DELETE', url)
|
||||||
|
|
||||||
|
|
||||||
|
class Resource(base.Resource):
|
||||||
|
"""Represents a particular instance of an object (tenant, user, etc).
|
||||||
|
|
||||||
|
This is pretty much just a bag for attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return copy.deepcopy(self._info)
|
374
magnumclient/common/httpclient.py
Normal file
374
magnumclient/common/httpclient.py
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2012 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
from keystoneclient import adapter
|
||||||
|
import six
|
||||||
|
import six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
|
from magnumclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
USER_AGENT = 'python-magnumclient'
|
||||||
|
CHUNKSIZE = 1024 * 64 # 64kB
|
||||||
|
|
||||||
|
API_VERSION = '/v1'
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPClient(object):
|
||||||
|
|
||||||
|
def __init__(self, endpoint, **kwargs):
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.auth_token = kwargs.get('token')
|
||||||
|
self.auth_ref = kwargs.get('auth_ref')
|
||||||
|
self.connection_params = self.get_connection_params(endpoint, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_connection_params(endpoint, **kwargs):
|
||||||
|
parts = urlparse.urlparse(endpoint)
|
||||||
|
|
||||||
|
# trim API version and trailing slash from endpoint
|
||||||
|
path = parts.path
|
||||||
|
path = path.rstrip('/').rstrip(API_VERSION)
|
||||||
|
|
||||||
|
_args = (parts.hostname, parts.port, path)
|
||||||
|
_kwargs = {'timeout': (float(kwargs.get('timeout'))
|
||||||
|
if kwargs.get('timeout') else 600)}
|
||||||
|
|
||||||
|
if parts.scheme == 'https':
|
||||||
|
_class = VerifiedHTTPSConnection
|
||||||
|
_kwargs['ca_file'] = kwargs.get('ca_file', None)
|
||||||
|
_kwargs['cert_file'] = kwargs.get('cert_file', None)
|
||||||
|
_kwargs['key_file'] = kwargs.get('key_file', None)
|
||||||
|
_kwargs['insecure'] = kwargs.get('insecure', False)
|
||||||
|
elif parts.scheme == 'http':
|
||||||
|
_class = six.moves.http_client.HTTPConnection
|
||||||
|
else:
|
||||||
|
msg = 'Unsupported scheme: %s' % parts.scheme
|
||||||
|
raise exceptions.EndpointException(msg)
|
||||||
|
|
||||||
|
return (_class, _args, _kwargs)
|
||||||
|
|
||||||
|
def get_connection(self):
|
||||||
|
_class = self.connection_params[0]
|
||||||
|
try:
|
||||||
|
return _class(*self.connection_params[1][0:2],
|
||||||
|
**self.connection_params[2])
|
||||||
|
except six.moves.http_client.InvalidURL:
|
||||||
|
raise exceptions.EndpointException()
|
||||||
|
|
||||||
|
def log_curl_request(self, method, url, kwargs):
|
||||||
|
curl = ['curl -i -X %s' % method]
|
||||||
|
|
||||||
|
for (key, value) in kwargs['headers'].items():
|
||||||
|
header = '-H \'%s: %s\'' % (key, value)
|
||||||
|
curl.append(header)
|
||||||
|
|
||||||
|
conn_params_fmt = [
|
||||||
|
('key_file', '--key %s'),
|
||||||
|
('cert_file', '--cert %s'),
|
||||||
|
('ca_file', '--cacert %s'),
|
||||||
|
]
|
||||||
|
for (key, fmt) in conn_params_fmt:
|
||||||
|
value = self.connection_params[2].get(key)
|
||||||
|
if value:
|
||||||
|
curl.append(fmt % value)
|
||||||
|
|
||||||
|
if self.connection_params[2].get('insecure'):
|
||||||
|
curl.append('-k')
|
||||||
|
|
||||||
|
if 'body' in kwargs:
|
||||||
|
curl.append('-d \'%s\'' % kwargs['body'])
|
||||||
|
|
||||||
|
curl.append('%s%s' % (self.endpoint, url))
|
||||||
|
LOG.debug(' '.join(curl))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log_http_response(resp, body=None):
|
||||||
|
status = (resp.version / 10.0, resp.status, resp.reason)
|
||||||
|
dump = ['\nHTTP/%.1f %s %s' % status]
|
||||||
|
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
|
||||||
|
dump.append('')
|
||||||
|
if body:
|
||||||
|
dump.extend([body, ''])
|
||||||
|
LOG.debug('\n'.join(dump))
|
||||||
|
|
||||||
|
def _make_connection_url(self, url):
|
||||||
|
(_class, _args, _kwargs) = self.connection_params
|
||||||
|
base_url = _args[2]
|
||||||
|
return '%s/%s' % (base_url, url.lstrip('/'))
|
||||||
|
|
||||||
|
def _extract_error_json(self, body):
|
||||||
|
error_json = {}
|
||||||
|
try:
|
||||||
|
body_json = json.loads(body)
|
||||||
|
if 'error_message' in body_json:
|
||||||
|
raw_msg = body_json['error_message']
|
||||||
|
error_json = json.loads(raw_msg)
|
||||||
|
except ValueError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return error_json
|
||||||
|
|
||||||
|
def _http_request(self, url, method, **kwargs):
|
||||||
|
"""Send an http request with the specified characteristics.
|
||||||
|
|
||||||
|
Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
|
||||||
|
as setting headers and error handling.
|
||||||
|
"""
|
||||||
|
# Copy the kwargs so we can reuse the original in case of redirects
|
||||||
|
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
||||||
|
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
||||||
|
if self.auth_token:
|
||||||
|
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
||||||
|
|
||||||
|
self.log_curl_request(method, url, kwargs)
|
||||||
|
conn = self.get_connection()
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn_url = self._make_connection_url(url)
|
||||||
|
conn.request(method, conn_url, **kwargs)
|
||||||
|
resp = conn.getresponse()
|
||||||
|
except socket.gaierror as e:
|
||||||
|
message = ("Error finding address for %(url)s: %(e)s"
|
||||||
|
% dict(url=url, e=e))
|
||||||
|
raise exceptions.EndpointNotFound(message)
|
||||||
|
except (socket.error, socket.timeout) as e:
|
||||||
|
endpoint = self.endpoint
|
||||||
|
message = ("Error communicating with %(endpoint)s %(e)s"
|
||||||
|
% dict(endpoint=endpoint, e=e))
|
||||||
|
raise exceptions.ConnectionRefused(message)
|
||||||
|
|
||||||
|
body_iter = ResponseBodyIterator(resp)
|
||||||
|
|
||||||
|
# Read body into string if it isn't obviously image data
|
||||||
|
body_str = None
|
||||||
|
if resp.getheader('content-type', None) != 'application/octet-stream':
|
||||||
|
body_str = ''.join([chunk for chunk in body_iter])
|
||||||
|
self.log_http_response(resp, body_str)
|
||||||
|
body_iter = six.StringIO(body_str)
|
||||||
|
else:
|
||||||
|
self.log_http_response(resp)
|
||||||
|
|
||||||
|
if 400 <= resp.status < 600:
|
||||||
|
LOG.warn("Request returned failure status.")
|
||||||
|
error_json = self._extract_error_json(body_str)
|
||||||
|
raise exceptions.from_response(
|
||||||
|
resp, error_json.get('faultstring'),
|
||||||
|
error_json.get('debuginfo'), method, url)
|
||||||
|
elif resp.status in (301, 302, 305):
|
||||||
|
# Redirected. Reissue the request to the new location.
|
||||||
|
return self._http_request(resp['location'], method, **kwargs)
|
||||||
|
elif resp.status == 300:
|
||||||
|
raise exceptions.from_response(resp, method=method, url=url)
|
||||||
|
|
||||||
|
return resp, body_iter
|
||||||
|
|
||||||
|
def json_request(self, method, url, **kwargs):
|
||||||
|
kwargs.setdefault('headers', {})
|
||||||
|
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||||
|
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||||
|
|
||||||
|
if 'body' in kwargs:
|
||||||
|
kwargs['body'] = json.dumps(kwargs['body'])
|
||||||
|
|
||||||
|
resp, body_iter = self._http_request(url, method, **kwargs)
|
||||||
|
content_type = resp.getheader('content-type', None)
|
||||||
|
|
||||||
|
if resp.status == 204 or resp.status == 205 or content_type is None:
|
||||||
|
return resp, list()
|
||||||
|
|
||||||
|
if 'application/json' in content_type:
|
||||||
|
body = ''.join([chunk for chunk in body_iter])
|
||||||
|
try:
|
||||||
|
body = json.loads(body)
|
||||||
|
except ValueError:
|
||||||
|
LOG.error('Could not decode response body as JSON')
|
||||||
|
else:
|
||||||
|
body = None
|
||||||
|
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
def raw_request(self, method, url, **kwargs):
|
||||||
|
kwargs.setdefault('headers', {})
|
||||||
|
kwargs['headers'].setdefault('Content-Type',
|
||||||
|
'application/octet-stream')
|
||||||
|
return self._http_request(url, method, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
|
||||||
|
"""httplib-compatibile connection using client-side SSL authentication
|
||||||
|
|
||||||
|
:see http://code.activestate.com/recipes/
|
||||||
|
577548-https-httplib-client-connection-with-certificate-v/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host, port, key_file=None, cert_file=None,
|
||||||
|
ca_file=None, timeout=None, insecure=False):
|
||||||
|
six.moves.http_client.HTTPSConnection.__init__(self, host, port,
|
||||||
|
key_file=key_file,
|
||||||
|
cert_file=cert_file)
|
||||||
|
self.key_file = key_file
|
||||||
|
self.cert_file = cert_file
|
||||||
|
if ca_file is not None:
|
||||||
|
self.ca_file = ca_file
|
||||||
|
else:
|
||||||
|
self.ca_file = self.get_system_ca_file()
|
||||||
|
self.timeout = timeout
|
||||||
|
self.insecure = insecure
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Connect to a host on a given (SSL) port.
|
||||||
|
|
||||||
|
If ca_file is pointing somewhere, use it to check Server Certificate.
|
||||||
|
|
||||||
|
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
|
||||||
|
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
|
||||||
|
ssl.wrap_socket(), which forces SSL to check server certificate against
|
||||||
|
our client certificate.
|
||||||
|
"""
|
||||||
|
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||||
|
|
||||||
|
if self._tunnel_host:
|
||||||
|
self.sock = sock
|
||||||
|
self._tunnel()
|
||||||
|
|
||||||
|
if self.insecure is True:
|
||||||
|
kwargs = {'cert_reqs': ssl.CERT_NONE}
|
||||||
|
else:
|
||||||
|
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
|
||||||
|
|
||||||
|
if self.cert_file:
|
||||||
|
kwargs['certfile'] = self.cert_file
|
||||||
|
if self.key_file:
|
||||||
|
kwargs['keyfile'] = self.key_file
|
||||||
|
|
||||||
|
self.sock = ssl.wrap_socket(sock, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_system_ca_file():
|
||||||
|
"""Return path to system default CA file."""
|
||||||
|
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
||||||
|
# Suse, FreeBSD/OpenBSD
|
||||||
|
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
|
||||||
|
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||||
|
'/etc/ssl/ca-bundle.pem',
|
||||||
|
'/etc/ssl/cert.pem']
|
||||||
|
for ca in ca_path:
|
||||||
|
if os.path.exists(ca):
|
||||||
|
return ca
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class SessionClient(adapter.LegacyJsonAdapter):
|
||||||
|
"""HTTP client based on Keystone client session."""
|
||||||
|
|
||||||
|
def _http_request(self, url, method, **kwargs):
|
||||||
|
kwargs.setdefault('user_agent', USER_AGENT)
|
||||||
|
kwargs.setdefault('auth', self.auth)
|
||||||
|
|
||||||
|
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||||
|
endpoint_filter.setdefault('interface', self.interface)
|
||||||
|
endpoint_filter.setdefault('service_type', self.service_type)
|
||||||
|
endpoint_filter.setdefault('region_name', self.region_name)
|
||||||
|
|
||||||
|
resp = self.session.request(url, method,
|
||||||
|
raise_exc=False, **kwargs)
|
||||||
|
|
||||||
|
if 400 <= resp.status_code < 600:
|
||||||
|
raise exceptions.from_response(resp)
|
||||||
|
elif resp.status_code in (301, 302, 305):
|
||||||
|
# Redirected. Reissue the request to the new location.
|
||||||
|
location = resp.headers.get('location')
|
||||||
|
resp = self._http_request(location, method, **kwargs)
|
||||||
|
elif resp.status_code == 300:
|
||||||
|
raise exceptions.from_response(resp, method=method, url=url)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def json_request(self, method, url, **kwargs):
|
||||||
|
kwargs.setdefault('headers', {})
|
||||||
|
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||||
|
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||||
|
|
||||||
|
if 'body' in kwargs:
|
||||||
|
kwargs['data'] = json.dumps(kwargs.pop('body'))
|
||||||
|
|
||||||
|
resp = self._http_request(url, method, **kwargs)
|
||||||
|
body = resp.content
|
||||||
|
content_type = resp.headers.get('content-type', None)
|
||||||
|
status = resp.status_code
|
||||||
|
if status == 204 or status == 205 or content_type is None:
|
||||||
|
return resp, list()
|
||||||
|
if 'application/json' in content_type:
|
||||||
|
try:
|
||||||
|
body = resp.json()
|
||||||
|
except ValueError:
|
||||||
|
LOG.error('Could not decode response body as JSON')
|
||||||
|
else:
|
||||||
|
body = None
|
||||||
|
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
def raw_request(self, method, url, **kwargs):
|
||||||
|
kwargs.setdefault('headers', {})
|
||||||
|
kwargs['headers'].setdefault('Content-Type',
|
||||||
|
'application/octet-stream')
|
||||||
|
return self._http_request(url, method, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseBodyIterator(object):
|
||||||
|
"""A class that acts as an iterator over an HTTP response."""
|
||||||
|
|
||||||
|
def __init__(self, resp):
|
||||||
|
self.resp = resp
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
while True:
|
||||||
|
yield self.next()
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
chunk = self.resp.read(CHUNKSIZE)
|
||||||
|
if chunk:
|
||||||
|
return chunk
|
||||||
|
else:
|
||||||
|
raise StopIteration()
|
||||||
|
|
||||||
|
|
||||||
|
def _construct_http_client(*args, **kwargs):
|
||||||
|
session = kwargs.pop('session', None)
|
||||||
|
auth = kwargs.pop('auth', None)
|
||||||
|
|
||||||
|
if session:
|
||||||
|
service_type = kwargs.pop('service_type', 'baremetal')
|
||||||
|
interface = kwargs.pop('endpoint_type', None)
|
||||||
|
region_name = kwargs.pop('region_name', None)
|
||||||
|
return SessionClient(session=session,
|
||||||
|
auth=auth,
|
||||||
|
interface=interface,
|
||||||
|
service_type=service_type,
|
||||||
|
region_name=region_name,
|
||||||
|
service_name=None,
|
||||||
|
user_agent='python-magnumclient')
|
||||||
|
else:
|
||||||
|
return HTTPClient(*args, **kwargs)
|
37
magnumclient/common/utils.py
Normal file
37
magnumclient/common/utils.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2012 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None):
|
||||||
|
"""Generate common filters for any list request.
|
||||||
|
|
||||||
|
:param marker: entity ID from which to start returning entities.
|
||||||
|
:param limit: maximum number of entities to return.
|
||||||
|
:param sort_key: field to use for sorting.
|
||||||
|
:param sort_dir: direction of sorting: 'asc' or 'desc'.
|
||||||
|
:returns: list of string filters.
|
||||||
|
"""
|
||||||
|
filters = []
|
||||||
|
if isinstance(limit, int) and limit > 0:
|
||||||
|
filters.append('limit=%s' % limit)
|
||||||
|
if marker is not None:
|
||||||
|
filters.append('marker=%s' % marker)
|
||||||
|
if sort_key is not None:
|
||||||
|
filters.append('sort_key=%s' % sort_key)
|
||||||
|
if sort_dir is not None:
|
||||||
|
filters.append('sort_dir=%s' % sort_dir)
|
||||||
|
return filters
|
61
magnumclient/exceptions.py
Normal file
61
magnumclient/exceptions.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from magnumclient.openstack.common.apiclient import exceptions
|
||||||
|
from magnumclient.openstack.common.apiclient.exceptions import * # noqa
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards
|
||||||
|
# compatibility.
|
||||||
|
InvalidEndpoint = EndpointException
|
||||||
|
CommunicationError = ConnectionRefused
|
||||||
|
HTTPBadRequest = BadRequest
|
||||||
|
HTTPInternalServerError = InternalServerError
|
||||||
|
HTTPNotFound = NotFound
|
||||||
|
HTTPServiceUnavailable = ServiceUnavailable
|
||||||
|
|
||||||
|
|
||||||
|
class AmbiguousAuthSystem(ClientException):
|
||||||
|
"""Could not obtain token and endpoint using provided credentials."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Alias for backwards compatibility
|
||||||
|
AmbigiousAuthSystem = AmbiguousAuthSystem
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAttribute(ClientException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def from_response(response, message=None, traceback=None, method=None,
|
||||||
|
url=None):
|
||||||
|
"""Return an HttpError instance based on response from httplib/requests."""
|
||||||
|
|
||||||
|
error_body = {}
|
||||||
|
if message:
|
||||||
|
error_body['message'] = message
|
||||||
|
if traceback:
|
||||||
|
error_body['details'] = traceback
|
||||||
|
|
||||||
|
if hasattr(response, 'status') and not hasattr(response, 'status_code'):
|
||||||
|
# NOTE(akurilin): These modifications around response object give
|
||||||
|
# ability to get all necessary information in method `from_response`
|
||||||
|
# from common code, which expecting response object from `requests`
|
||||||
|
# library instead of object from `httplib/httplib2` library.
|
||||||
|
response.status_code = response.status
|
||||||
|
response.headers = {
|
||||||
|
'Content-Type': response.getheader('content-type', "")}
|
||||||
|
response.json = lambda: {'error': error_body}
|
||||||
|
|
||||||
|
return exceptions.from_response(response, message, url)
|
@ -588,6 +588,7 @@ class OpenStackMagnumShell(object):
|
|||||||
project_id=os_tenant_id,
|
project_id=os_tenant_id,
|
||||||
project_name=os_tenant_name,
|
project_name=os_tenant_name,
|
||||||
auth_url=os_auth_url,
|
auth_url=os_auth_url,
|
||||||
|
service_type=service_type,
|
||||||
magnum_url=bypass_url)
|
magnum_url=bypass_url)
|
||||||
|
|
||||||
args.func(self.cs, args)
|
args.func(self.cs, args)
|
||||||
|
Loading…
Reference in New Issue
Block a user