summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ceilometer/tests/tempest/api/test_telemetry_notification_api.py6
-rw-r--r--ceilometer/tests/tempest/service/client.py6
-rw-r--r--ceilometer/tests/tempest/service/images/__init__.py0
-rw-r--r--ceilometer/tests/tempest/service/images/glance_http.py361
-rw-r--r--ceilometer/tests/tempest/service/images/v2/__init__.py0
-rw-r--r--ceilometer/tests/tempest/service/images/v2/images_client.py245
6 files changed, 7 insertions, 611 deletions
diff --git a/ceilometer/tests/tempest/api/test_telemetry_notification_api.py b/ceilometer/tests/tempest/api/test_telemetry_notification_api.py
index e38702a9..d5a1eb0e 100644
--- a/ceilometer/tests/tempest/api/test_telemetry_notification_api.py
+++ b/ceilometer/tests/tempest/api/test_telemetry_notification_api.py
@@ -12,9 +12,11 @@
# Change-Id: I14e16a1a7d9813b324ee40545c07f0e88fb637b7
+import six
import testtools
from ceilometer.tests.tempest.api import base
+from tempest.common.utils import data_utils
from tempest import config
from tempest.lib import decorators
from tempest import test
@@ -44,7 +46,9 @@ class TelemetryNotificationAPITest(base.BaseTelemetryTest):
def test_check_glance_v2_notifications(self):
body = self.create_image(self.image_client_v2, visibility='private')
- self.image_client_v2.store_image_file(body['id'], "file")
+ file_content = data_utils.random_bytes()
+ image_file = six.BytesIO(file_content)
+ self.image_client_v2.store_image_file(body['id'], image_file)
self.image_client_v2.show_image_file(body['id'])
query = 'resource', 'eq', body['id']
diff --git a/ceilometer/tests/tempest/service/client.py b/ceilometer/tests/tempest/service/client.py
index 6fe637f3..332b4503 100644
--- a/ceilometer/tests/tempest/service/client.py
+++ b/ceilometer/tests/tempest/service/client.py
@@ -22,13 +22,11 @@ from tempest.lib.services.compute import flavors_client as flavor_cli
from tempest.lib.services.compute import floating_ips_client as floatingip_cli
from tempest.lib.services.compute import networks_client as network_cli
from tempest.lib.services.compute import servers_client as server_cli
+from tempest.lib.services.image.v2 import images_client as img_cli_v2
from tempest import manager
from tempest.services.object_storage import container_client as container_cli
from tempest.services.object_storage import object_client as obj_cli
-from ceilometer.tests.tempest.service.images.v2 import images_client as \
- img_cli_v2
-
CONF = config.CONF
@@ -121,7 +119,7 @@ class Manager(manager.Manager):
compute_params.update(default_params)
image_params = {
- 'catalog_type': CONF.image.catalog_type,
+ 'service': CONF.image.catalog_type,
'region': CONF.image.region or CONF.identity.region,
'endpoint_type': CONF.image.endpoint_type,
'build_interval': CONF.image.build_interval,
diff --git a/ceilometer/tests/tempest/service/images/__init__.py b/ceilometer/tests/tempest/service/images/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/ceilometer/tests/tempest/service/images/__init__.py
+++ /dev/null
diff --git a/ceilometer/tests/tempest/service/images/glance_http.py b/ceilometer/tests/tempest/service/images/glance_http.py
deleted file mode 100644
index 19a9d981..00000000
--- a/ceilometer/tests/tempest/service/images/glance_http.py
+++ /dev/null
@@ -1,361 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# 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.
-
-# Originally copied from python-glanceclient
-
-import copy
-import hashlib
-import posixpath
-import re
-import socket
-import struct
-
-import OpenSSL
-from oslo_log import log as logging
-import six
-from six import moves
-from six.moves import http_client as httplib
-from six.moves.urllib import parse as urlparse
-
-from ceilometer.tests.tempest import exceptions as exc
-
-LOG = logging.getLogger(__name__)
-USER_AGENT = 'tempest'
-CHUNKSIZE = 1024 * 64 # 64kB
-TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$')
-
-
-class HTTPClient(object):
-
- def __init__(self, auth_provider, filters, **kwargs):
- self.auth_provider = auth_provider
- self.filters = filters
- self.endpoint = auth_provider.base_url(filters)
- endpoint_parts = urlparse.urlparse(self.endpoint)
- self.endpoint_scheme = endpoint_parts.scheme
- self.endpoint_hostname = endpoint_parts.hostname
- self.endpoint_port = endpoint_parts.port
-
- self.connection_class = self._get_connection_class(
- self.endpoint_scheme)
- self.connection_kwargs = self._get_connection_kwargs(
- self.endpoint_scheme, **kwargs)
-
- @staticmethod
- def _get_connection_class(scheme):
- if scheme == 'https':
- return VerifiedHTTPSConnection
- else:
- return httplib.HTTPConnection
-
- @staticmethod
- def _get_connection_kwargs(scheme, **kwargs):
- _kwargs = {'timeout': float(kwargs.get('timeout', 600))}
-
- if scheme == 'https':
- _kwargs['ca_certs'] = kwargs.get('ca_certs', None)
- _kwargs['cert_file'] = kwargs.get('cert_file', None)
- _kwargs['key_file'] = kwargs.get('key_file', None)
- _kwargs['insecure'] = kwargs.get('insecure', False)
- _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)
-
- return _kwargs
-
- def _get_connection(self):
- _class = self.connection_class
- try:
- return _class(self.endpoint_hostname, self.endpoint_port,
- **self.connection_kwargs)
- except httplib.InvalidURL:
- raise exc.EndpointNotFound
-
- 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)
-
- self._log_request(method, url, kwargs['headers'])
-
- conn = self._get_connection()
-
- try:
- url_parts = urlparse.urlparse(url)
- conn_url = posixpath.normpath(url_parts.path)
- LOG.debug('Actual Path: {path}'.format(path=conn_url))
- if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
- conn.putrequest(method, conn_url)
- for header, value in kwargs['headers'].items():
- conn.putheader(header, value)
- conn.endheaders()
- chunk = kwargs['body'].read(CHUNKSIZE)
- # Chunk it, baby...
- while chunk:
- conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
- chunk = kwargs['body'].read(CHUNKSIZE)
- conn.send('0\r\n\r\n')
- else:
- conn.request(method, conn_url, **kwargs)
- resp = conn.getresponse()
- except socket.gaierror as e:
- message = ("Error finding address for %(url)s: %(e)s" %
- {'url': url, 'e': e})
- raise exc.EndpointNotFound(message)
- except (socket.error, socket.timeout) as e:
- message = ("Error communicating with %(endpoint)s %(e)s" %
- {'endpoint': self.endpoint, 'e': e})
- raise exc.TimeoutException(message)
-
- body_iter = ResponseBodyIterator(resp)
- # Read body into string if it isn't obviously image data
- if resp.getheader('content-type', None) != 'application/octet-stream':
- body_str = ''.join([body_chunk for body_chunk in body_iter])
- body_iter = six.StringIO(body_str)
- self._log_response(resp, None)
- else:
- self._log_response(resp, body_iter)
-
- return resp, body_iter
-
- def _log_request(self, method, url, headers):
- LOG.info('Request: ' + method + ' ' + url)
- if headers:
- headers_out = headers
- if 'X-Auth-Token' in headers and headers['X-Auth-Token']:
- token = headers['X-Auth-Token']
- if len(token) > 64 and TOKEN_CHARS_RE.match(token):
- headers_out = headers.copy()
- headers_out['X-Auth-Token'] = "<Token omitted>"
- LOG.info('Request Headers: ' + str(headers_out))
-
- def _log_response(self, resp, body):
- status = str(resp.status)
- LOG.info("Response Status: " + status)
- if resp.getheaders():
- LOG.info('Response Headers: ' + str(resp.getheaders()))
- if body:
- str_body = str(body)
- length = len(body)
- LOG.info('Response Body: ' + str_body[:2048])
- if length >= 2048:
- self.LOG.debug("Large body (%d) md5 summary: %s", length,
- hashlib.md5(str_body).hexdigest())
-
- def raw_request(self, method, url, **kwargs):
- kwargs.setdefault('headers', {})
- kwargs['headers'].setdefault('Content-Type',
- 'application/octet-stream')
- if 'body' in kwargs:
- if (hasattr(kwargs['body'], 'read')
- and method.lower() in ('post', 'put')):
- # We use 'Transfer-Encoding: chunked' because
- # body size may not always be known in advance.
- kwargs['headers']['Transfer-Encoding'] = 'chunked'
-
- # Decorate the request with auth
- req_url, kwargs['headers'], kwargs['body'] = \
- self.auth_provider.auth_request(
- method=method, url=url, headers=kwargs['headers'],
- body=kwargs.get('body', None), filters=self.filters)
- return self._http_request(req_url, method, **kwargs)
-
-
-class OpenSSLConnectionDelegator(object):
- """An OpenSSL.SSL.Connection delegator.
-
- Supplies an additional 'makefile' method which httplib requires
- and is not present in OpenSSL.SSL.Connection.
-
- Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
- a delegator must be used.
- """
- def __init__(self, *args, **kwargs):
- self.connection = OpenSSL.SSL.Connection(*args, **kwargs)
-
- def __getattr__(self, name):
- return getattr(self.connection, name)
-
- def makefile(self, *args, **kwargs):
- # Ensure the socket is closed when this file is closed
- kwargs['close'] = True
- return socket._fileobject(self.connection, *args, **kwargs)
-
-
-class VerifiedHTTPSConnection(httplib.HTTPSConnection):
- """Extended HTTPSConnection which uses OpenSSL library for enhanced SSL
-
- Note: Much of this functionality can eventually be replaced
- with native Python 3.3 code.
- """
- def __init__(self, host, port=None, key_file=None, cert_file=None,
- ca_certs=None, timeout=None, insecure=False,
- ssl_compression=True):
- httplib.HTTPSConnection.__init__(self, host, port,
- key_file=key_file,
- cert_file=cert_file)
- self.key_file = key_file
- self.cert_file = cert_file
- self.timeout = timeout
- self.insecure = insecure
- self.ssl_compression = ssl_compression
- self.ca_certs = ca_certs
- self.setcontext()
-
- @staticmethod
- def host_matches_cert(host, x509):
- """Verify that the x509 certificate we have received from 'host'
-
- Identifies the server we are connecting to, ie that the certificate's
- Common Name or a Subject Alternative Name matches 'host'.
- """
- # First see if we can match the CN
- if x509.get_subject().commonName == host:
- return True
-
- # Also try Subject Alternative Names for a match
- san_list = None
- for i in moves.xrange(x509.get_extension_count()):
- ext = x509.get_extension(i)
- if ext.get_short_name() == 'subjectAltName':
- san_list = str(ext)
- for san in ''.join(san_list.split()).split(','):
- if san == "DNS:%s" % host:
- return True
-
- # Server certificate does not match host
- msg = ('Host "%s" does not match x509 certificate contents: '
- 'CommonName "%s"' % (host, x509.get_subject().commonName))
- if san_list is not None:
- msg = msg + ', subjectAltName "%s"' % san_list
- raise exc.SSLCertificateError(msg)
-
- def verify_callback(self, connection, x509, errnum,
- depth, preverify_ok):
- if x509.has_expired():
- msg = "SSL Certificate expired on '%s'" % x509.get_notAfter()
- raise exc.SSLCertificateError(msg)
-
- if depth == 0 and preverify_ok is True:
- # We verify that the host matches against the last
- # certificate in the chain
- return self.host_matches_cert(self.host, x509)
- else:
- # Pass through OpenSSL's default result
- return preverify_ok
-
- def setcontext(self):
- """Set up the OpenSSL context."""
- self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
-
- if self.ssl_compression is False:
- self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION
-
- if self.insecure is not True:
- self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
- self.verify_callback)
- else:
- self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
- self.verify_callback)
-
- if self.cert_file:
- try:
- self.context.use_certificate_file(self.cert_file)
- except Exception as e:
- msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e)
- raise exc.SSLConfigurationError(msg)
- if self.key_file is None:
- # We support having key and cert in same file
- try:
- self.context.use_privatekey_file(self.cert_file)
- except Exception as e:
- msg = ('No key file specified and unable to load key '
- 'from "%s" %s' % (self.cert_file, e))
- raise exc.SSLConfigurationError(msg)
-
- if self.key_file:
- try:
- self.context.use_privatekey_file(self.key_file)
- except Exception as e:
- msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
- raise exc.SSLConfigurationError(msg)
-
- if self.ca_certs:
- try:
- self.context.load_verify_locations(self.ca_certs)
- except Exception as e:
- msg = 'Unable to load CA from "%s" %s' % (self.ca_certs, e)
- raise exc.SSLConfigurationError(msg)
- else:
- self.context.set_default_verify_paths()
-
- def connect(self):
- """Connect to SSL port and apply per-connection parameters."""
- try:
- addresses = socket.getaddrinfo(self.host,
- self.port,
- socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- except OSError as msg:
- raise exc.RestClientException(msg)
- for res in addresses:
- af, socktype, proto, canonname, sa = res
- sock = socket.socket(af, socket.SOCK_STREAM)
-
- if self.timeout is not None:
- # '0' microseconds
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO,
- struct.pack('LL', self.timeout, 0))
- self.sock = OpenSSLConnectionDelegator(self.context, sock)
- try:
- self.sock.connect(sa)
- except OSError as msg:
- if self.sock:
- self.sock = None
- continue
- break
- if self.sock is None:
- # Happen only when all results have failed.
- raise exc.RestClientException('Cannot connect to %s' % self.host)
-
- def close(self):
- if self.sock:
- # Remove the reference to the socket but don't close it yet.
- # Response close will close both socket and associated
- # file. Closing socket too soon will cause response
- # reads to fail with socket IO error 'Bad file descriptor'.
- self.sock = None
- httplib.HTTPSConnection.close(self)
-
-
-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()
diff --git a/ceilometer/tests/tempest/service/images/v2/__init__.py b/ceilometer/tests/tempest/service/images/v2/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/ceilometer/tests/tempest/service/images/v2/__init__.py
+++ /dev/null
diff --git a/ceilometer/tests/tempest/service/images/v2/images_client.py b/ceilometer/tests/tempest/service/images/v2/images_client.py
deleted file mode 100644
index d0cbadc6..00000000
--- a/ceilometer/tests/tempest/service/images/v2/images_client.py
+++ /dev/null
@@ -1,245 +0,0 @@
-# Copyright 2013 IBM Corp.
-# 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 oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-
-from ceilometer.tests.tempest.service.images import glance_http
-
-
-class ImagesClient(rest_client.RestClient):
-
- def __init__(self, auth_provider, catalog_type, region, **kwargs):
- super(ImagesClient, self).__init__(
- auth_provider, catalog_type, region, **kwargs)
- self._http = None
- self.dscv = kwargs.get("disable_ssl_certificate_validation")
- self.ca_certs = kwargs.get("ca_certs")
-
- def _get_http(self):
- return glance_http.HTTPClient(auth_provider=self.auth_provider,
- filters=self.filters,
- insecure=self.dscv,
- ca_certs=self.ca_certs)
-
- @property
- def http(self):
- if self._http is None:
- self._http = self._get_http()
- return self._http
-
- def update_image(self, image_id, patch):
- """Update an image.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateImage-v2
- """
- data = json.dumps(patch)
- headers = {"Content-Type": "application/openstack-images-v2.0"
- "-json-patch"}
- resp, body = self.patch('v2/images/%s' % image_id, data, headers)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def create_image(self, **kwargs):
- """Create an image.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createImage-v2
- """
- data = json.dumps(kwargs)
- resp, body = self.post('v2/images', data)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def deactivate_image(self, image_id):
- url = 'v2/images/%s/actions/deactivate' % image_id
- resp, body = self.post(url, None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def reactivate_image(self, image_id):
- url = 'v2/images/%s/actions/reactivate' % image_id
- resp, body = self.post(url, None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_image(self, image_id):
- url = 'v2/images/%s' % image_id
- resp, _ = self.delete(url)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def list_images(self, params=None):
- url = 'v2/images'
-
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_image(self, image_id):
- url = 'v2/images/%s' % image_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def is_resource_deleted(self, id):
- try:
- self.show_image(id)
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'image'
-
- def store_image_file(self, image_id, data):
- url = 'v2/images/%s/file' % image_id
- headers = {'Content-Type': 'application/octet-stream'}
- resp, body = self.http.raw_request('PUT', url, headers=headers,
- body=data)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_image_file(self, image_id):
- url = 'v2/images/%s/file' % image_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBodyData(resp, body)
-
- def add_image_tag(self, image_id, tag):
- url = 'v2/images/%s/tags/%s' % (image_id, tag)
- resp, body = self.put(url, body=None)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_image_tag(self, image_id, tag):
- url = 'v2/images/%s/tags/%s' % (image_id, tag)
- resp, _ = self.delete(url)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def list_image_members(self, image_id):
- url = 'v2/images/%s/members' % image_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def create_image_member(self, image_id, **kwargs):
- """Create an image member.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createImageMember-v2
- """
- url = 'v2/images/%s/members' % image_id
- data = json.dumps(kwargs)
- resp, body = self.post(url, data)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_image_member(self, image_id, member_id, **kwargs):
- """Update an image member.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateImageMember-v2
- """
- url = 'v2/images/%s/members/%s' % (image_id, member_id)
- data = json.dumps(kwargs)
- resp, body = self.put(url, data)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_image_member(self, image_id, member_id):
- url = 'v2/images/%s/members/%s' % (image_id, member_id)
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, json.loads(body))
-
- def delete_image_member(self, image_id, member_id):
- url = 'v2/images/%s/members/%s' % (image_id, member_id)
- resp, _ = self.delete(url)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)
-
- def show_schema(self, schema):
- url = 'v2/schemas/%s' % schema
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def list_resource_types(self):
- url = '/v2/metadefs/resource_types'
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def create_namespace(self, **kwargs):
- """Create a namespace.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#createNamespace-v2
- """
- data = json.dumps(kwargs)
- resp, body = self.post('/v2/metadefs/namespaces', data)
- self.expected_success(201, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_namespace(self, namespace):
- url = '/v2/metadefs/namespaces/%s' % namespace
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_namespace(self, namespace, **kwargs):
- """Update a namespace.
-
- Available params: see http://developer.openstack.org/
- api-ref-image-v2.html#updateNamespace-v2
- """
- # NOTE: On Glance API, we need to pass namespace on both URI
- # and a request body.
- params = {'namespace': namespace}
- params.update(kwargs)
- data = json.dumps(params)
- url = '/v2/metadefs/namespaces/%s' % namespace
- resp, body = self.put(url, body=data)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_namespace(self, namespace):
- url = '/v2/metadefs/namespaces/%s' % namespace
- resp, _ = self.delete(url)
- self.expected_success(204, resp.status)
- return rest_client.ResponseBody(resp)