Encode/Decode data when copying files

This is the client side change of server patch [1]. If files
are copied to container, the file data will be encoded at
client side and decoded at server side. If files are copied
from container, the file data will be encoded at server
side and decoded at client side.

[1] https://review.openstack.org/#/c/599202/

Depends-On: https://review.openstack.org/#/c/599202/
Change-Id: Id9fe25e06052d46dcc33a1e05100ef7a2d7db018
Closes-Bug: #1789777
This commit is contained in:
Hongbin Lu
2018-09-01 22:50:10 +00:00
parent ac6ee94c84
commit c04d457e08
7 changed files with 43 additions and 16 deletions

View File

@@ -31,7 +31,7 @@ if not LOG.handlers:
HEADER_NAME = "OpenStack-API-Version"
SERVICE_TYPE = "container"
MIN_API_VERSION = '1.1'
MAX_API_VERSION = '1.24'
MAX_API_VERSION = '1.25'
DEFAULT_API_VERSION = MAX_API_VERSION
_SUBSTITUTIONS = {}

View File

@@ -14,11 +14,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import base64
import binascii
import json
import os
import re
from oslo_utils import netutils
import six
from six.moves.urllib import parse
from six.moves.urllib import request
from zunclient.common.apiclient import exceptions as apiexec
@@ -364,3 +367,17 @@ def list_container_networks(networks):
utils.print_list(networks, columns,
{'fixed_ips': format_network_fixed_ips},
sortby_index=None)
def encode_file_data(data):
if six.PY3 and isinstance(data, str):
data = data.encode('utf-8')
return base64.b64encode(data).decode('utf-8')
def decode_file_data(data):
# Py3 raises binascii.Error instead of TypeError as in Py27
try:
return base64.b64decode(data)
except (TypeError, binascii.Error):
raise exc.CommandError(_('Invalid Base 64 file data.'))

View File

@@ -1068,7 +1068,7 @@ class CopyContainer(command.Command):
res = client.containers.get_archive(**opts)
dest_path = parsed_args.destination
tardata = io.BytesIO(res['data'].encode())
tardata = io.BytesIO(res['data'])
with closing(tarfile.open(fileobj=tardata)) as tar:
tar.extractall(dest_path)

View File

@@ -246,7 +246,7 @@ class ShellTest(utils.TestCase):
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='', profile=None,
endpoint_override=None, insecure=False, cacert=None,
version=api_versions.APIVersion('1.24'))
version=api_versions.APIVersion('1.25'))
def test_main_option_region(self):
self.make_env()
@@ -274,7 +274,7 @@ class ShellTest(utils.TestCase):
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='', profile=None,
endpoint_override=None, insecure=False, cacert=None,
version=api_versions.APIVersion('1.24'))
version=api_versions.APIVersion('1.25'))
@mock.patch('zunclient.client.Client')
def test_main_endpoint_internal(self, mock_client):
@@ -288,7 +288,7 @@ class ShellTest(utils.TestCase):
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='', profile=None,
endpoint_override=None, insecure=False, cacert=None,
version=api_versions.APIVersion('1.24'))
version=api_versions.APIVersion('1.25'))
class ShellTestKeystoneV3(ShellTest):
@@ -320,4 +320,4 @@ class ShellTestKeystoneV3(ShellTest):
user_domain_id='', user_domain_name='Default',
endpoint_override=None, insecure=False, profile=None,
cacert=None,
version=api_versions.APIVersion('1.24'))
version=api_versions.APIVersion('1.25'))

View File

@@ -14,6 +14,7 @@ import copy
from six.moves.urllib import parse
import testtools
from testtools import matchers
from zunclient.common import utils as zun_utils
from zunclient import exceptions
from zunclient.tests.unit import utils
from zunclient.v1 import containers
@@ -289,7 +290,7 @@ fake_responses = {
{
'GET': (
{},
None,
{'data': data},
),
},
'/v1/containers/%s/put_archive?%s'
@@ -297,7 +298,7 @@ fake_responses = {
{
'POST': (
{},
{'data': data},
None,
),
},
'/v1/containers/%s/stats?%s'
@@ -653,25 +654,25 @@ class ContainerManagerTest(testtools.TestCase):
self.assertIsNone(containers)
def test_containers_get_archive(self):
containers = self.mgr.get_archive(CONTAINER1['id'], path)
response = self.mgr.get_archive(CONTAINER1['id'], path)
expect = [
('GET', '/v1/containers/%s/get_archive?%s'
% (CONTAINER1['id'], parse.urlencode({'path': path})),
{'Content-Length': '0'}, None)
]
self.assertEqual(expect, self.api.calls)
self.assertIsNone(containers)
self.assertEqual(zun_utils.decode_file_data(data), response['data'])
def test_containers_put_archive(self):
containers = self.mgr.put_archive(CONTAINER1['id'], path, data)
response = self.mgr.put_archive(CONTAINER1['id'], path, data)
expect = [
('POST', '/v1/containers/%s/put_archive?%s'
% (CONTAINER1['id'], parse.urlencode({'path': path})),
{'Content-Length': '0'},
{'data': data})
{'data': zun_utils.encode_file_data(data)})
]
self.assertEqual(expect, self.api.calls)
self.assertTrue(containers)
self.assertTrue(response)
def test_containers_commit(self):
containers = self.mgr.commit(CONTAINER1['id'], repo, tag)

View File

@@ -196,10 +196,19 @@ class ContainerManager(base.Manager):
qparams={'ps_args': ps_args})[1]
def get_archive(self, id, path):
return self._action(id, '/get_archive', method='GET',
qparams={'path': path})[1]
res = self._action(id, '/get_archive', method='GET',
qparams={'path': path})[1]
# API version 1.25 or later will return Base64-encoded data
if self.api_version >= api_versions.APIVersion("1.25"):
res['data'] = utils.decode_file_data(res['data'])
else:
res['data'] = res['data'].encode()
return res
def put_archive(self, id, path, data):
# API version 1.25 or later will expect Base64-encoded data
if self.api_version >= api_versions.APIVersion("1.25"):
data = utils.encode_file_data(data)
return self._action(id, '/put_archive',
qparams={'path': path},
body={'data': data})

View File

@@ -865,7 +865,7 @@ def do_cp(cs, args):
res = cs.containers.get_archive(**opts)
dest_path = args.destination
tardata = io.BytesIO(res['data'].encode())
tardata = io.BytesIO(res['data'])
with closing(tarfile.open(fileobj=tardata)) as tar:
tar.extractall(dest_path)