py3: Miscellaneous fixes
* Use base64 from oslo_serialization * Use six.BytesIO instead of StringIO * Use sys.maxsize instead of sys.maxint * Use sorted with a key argument to sort by * Use range instead of moves.xrange or xrange * Use floor division instead of float division * Use 'r+' to open a file than 'rw' * Use six.int2byte instead of chr * Stop using conn.strict in response_class() * Tests: Only compare length for bytes, not assertRegex These changes are tested here: I3a1dfde76009cbac1d419e0ff3f1d20c4a2e99c3 Change-Id: Iab9c23e497956e45c5d04a3d6513acf1b026aafb
This commit is contained in:
parent
d00166fb46
commit
12dc9735a5
|
@ -71,7 +71,7 @@ class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
|
|||
body = body['image'] if 'image' in body else body
|
||||
cls.image_id = body['id']
|
||||
cls.images.append(cls.image_id)
|
||||
image_file = six.StringIO(('*' * 1024))
|
||||
image_file = six.BytesIO((b'*' * 1024))
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.glance_client.update_image(cls.image_id, data=image_file)
|
||||
else:
|
||||
|
|
|
@ -78,7 +78,7 @@ class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
|
|||
# Wait 1 second between creation and upload to ensure a delta
|
||||
# between created_at and updated_at.
|
||||
time.sleep(1)
|
||||
image_file = six.StringIO(('*' * 1024))
|
||||
image_file = six.BytesIO((b'*' * 1024))
|
||||
if CONF.image_feature_enabled.api_v1:
|
||||
cls.glance_client.update_image(image_id, data=image_file)
|
||||
else:
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from six import moves
|
||||
|
||||
from tempest.api.compute import base
|
||||
from tempest.common import waiters
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
|
@ -38,7 +36,7 @@ class ListServersNegativeTestJSON(base.BaseV2ComputeTest):
|
|||
# tearDownClass method of the super-class.
|
||||
cls.existing_fixtures = []
|
||||
cls.deleted_fixtures = []
|
||||
for x in moves.xrange(2):
|
||||
for x in range(2):
|
||||
srv = cls.create_test_server(wait_until='ACTIVE')
|
||||
cls.existing_fixtures.append(srv)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import base64
|
||||
from oslo_serialization import base64
|
||||
|
||||
from tempest.api.compute import base
|
||||
from tempest.common.utils.linux import remote_client
|
||||
|
@ -55,7 +55,7 @@ class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
|
|||
file_contents = 'This is a test file.'
|
||||
file_path = '/test.txt'
|
||||
personality = [{'path': file_path,
|
||||
'contents': base64.b64encode(file_contents)}]
|
||||
'contents': base64.encode_as_text(file_contents)}]
|
||||
password = data_utils.rand_password()
|
||||
created_server = self.create_test_server(personality=personality,
|
||||
adminPass=password,
|
||||
|
@ -79,7 +79,7 @@ class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
|
|||
server_id = server['id']
|
||||
file_contents = 'Test server rebuild.'
|
||||
personality = [{'path': 'rebuild.txt',
|
||||
'contents': base64.b64encode(file_contents)}]
|
||||
'contents': base64.encode_as_text(file_contents)}]
|
||||
rebuilt_server = self.client.rebuild_server(server_id,
|
||||
self.image_ref_alt,
|
||||
personality=personality)
|
||||
|
@ -100,7 +100,8 @@ class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
|
|||
for i in range(0, int(max_file_limit) + 1):
|
||||
path = 'etc/test' + str(i) + '.txt'
|
||||
personality.append({'path': path,
|
||||
'contents': base64.b64encode(file_contents)})
|
||||
'contents': base64.encode_as_text(
|
||||
file_contents)})
|
||||
# A 403 Forbidden or 413 Overlimit (old behaviour) exception
|
||||
# will be raised when out of quota
|
||||
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
|
||||
|
@ -120,7 +121,7 @@ class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
|
|||
path = '/etc/test' + str(i) + '.txt'
|
||||
person.append({
|
||||
'path': path,
|
||||
'contents': base64.b64encode(file_contents),
|
||||
'contents': base64.encode_as_text(file_contents),
|
||||
})
|
||||
password = data_utils.rand_password()
|
||||
created_server = self.create_test_server(personality=person,
|
||||
|
|
|
@ -302,7 +302,7 @@ class ServersNegativeTestJSON(base.BaseV2ComputeTest):
|
|||
# Pass a server ID that exceeds length limit to delete server
|
||||
|
||||
self.assertRaises(lib_exc.NotFound, self.client.delete_server,
|
||||
sys.maxint + 1)
|
||||
sys.maxsize + 1)
|
||||
|
||||
@test.attr(type=['negative'])
|
||||
@test.idempotent_id('c5fa6041-80cd-483b-aa6d-4e45f19d093c')
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from six import moves
|
||||
|
||||
from tempest.api.identity import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
@ -27,7 +25,7 @@ class RolesTestJSON(base.BaseIdentityV2AdminTest):
|
|||
def resource_setup(cls):
|
||||
super(RolesTestJSON, cls).resource_setup()
|
||||
cls.roles = list()
|
||||
for _ in moves.xrange(5):
|
||||
for _ in range(5):
|
||||
role_name = data_utils.rand_name(name='role')
|
||||
role = cls.roles_client.create_role(name=role_name)['role']
|
||||
cls.roles.append(role)
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from six import moves
|
||||
|
||||
from tempest.api.identity import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
|
@ -84,7 +82,7 @@ class ServicesTestJSON(base.BaseIdentityV2AdminTest):
|
|||
def test_list_services(self):
|
||||
# Create, List, Verify and Delete Services
|
||||
services = []
|
||||
for _ in moves.xrange(3):
|
||||
for _ in range(3):
|
||||
name = data_utils.rand_name('service')
|
||||
s_type = data_utils.rand_name('type')
|
||||
description = data_utils.rand_name('description')
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from six import moves
|
||||
|
||||
from tempest.api.identity import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.lib.common.utils import test_utils
|
||||
|
@ -27,7 +25,7 @@ class TenantsTestJSON(base.BaseIdentityV2AdminTest):
|
|||
def test_tenant_list_delete(self):
|
||||
# Create several tenants and delete them
|
||||
tenants = []
|
||||
for _ in moves.xrange(3):
|
||||
for _ in range(3):
|
||||
tenant_name = data_utils.rand_name(name='tenant-new')
|
||||
tenant = self.tenants_client.create_tenant(
|
||||
name=tenant_name)['tenant']
|
||||
|
|
|
@ -91,7 +91,8 @@ class GroupsV3TestJSON(base.BaseIdentityV3AdminTest):
|
|||
|
||||
# list users in group
|
||||
group_users = self.groups_client.list_group_users(group['id'])['users']
|
||||
self.assertEqual(sorted(users), sorted(group_users))
|
||||
self.assertEqual(sorted(users, key=lambda k: k['name']),
|
||||
sorted(group_users, key=lambda k: k['name']))
|
||||
# check and delete user in group
|
||||
for user in users:
|
||||
self.groups_client.check_group_user_existence(
|
||||
|
@ -118,7 +119,8 @@ class GroupsV3TestJSON(base.BaseIdentityV3AdminTest):
|
|||
self.groups_client.add_group_user(group['id'], user['id'])
|
||||
# list groups which user belongs to
|
||||
user_groups = self.users_client.list_user_groups(user['id'])['groups']
|
||||
self.assertEqual(sorted(groups), sorted(user_groups))
|
||||
self.assertEqual(sorted(groups, key=lambda k: k['name']),
|
||||
sorted(user_groups, key=lambda k: k['name']))
|
||||
self.assertEqual(2, len(user_groups))
|
||||
|
||||
@test.idempotent_id('cc9a57a5-a9ed-4f2d-a29f-4f979a06ec71')
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from six import moves
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from tempest.api.image import base
|
||||
|
@ -42,7 +42,7 @@ class BasicAdminOperationsImagesTest(base.BaseV2ImageAdminTest):
|
|||
self.addCleanup(self.client.delete_image, image_id)
|
||||
# upload an image file
|
||||
content = data_utils.random_bytes()
|
||||
image_file = moves.cStringIO(content)
|
||||
image_file = six.BytesIO(content)
|
||||
self.client.store_image_file(image_id, image_file)
|
||||
# deactivate image
|
||||
self.admin_client.deactivate_image(image_id)
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from six import moves
|
||||
import six
|
||||
|
||||
from tempest.common import image as common_image
|
||||
from tempest.common.utils import data_utils
|
||||
|
@ -118,7 +118,7 @@ class BaseV1ImageMembersTest(BaseV1ImageTest):
|
|||
cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
|
||||
|
||||
def _create_image(self):
|
||||
image_file = moves.cStringIO(data_utils.random_bytes())
|
||||
image_file = six.BytesIO(data_utils.random_bytes())
|
||||
image = self.create_image(container_format='bare',
|
||||
disk_format='raw',
|
||||
is_public=False,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from six import moves
|
||||
import six
|
||||
|
||||
from tempest.api.image import base
|
||||
from tempest.common import image as common_image
|
||||
|
@ -63,7 +63,7 @@ class CreateRegisterImagesTest(base.BaseV1ImageTest):
|
|||
self.assertEqual(val, body.get('properties')[key])
|
||||
|
||||
# Now try uploading an image file
|
||||
image_file = moves.cStringIO(data_utils.random_bytes())
|
||||
image_file = six.BytesIO(data_utils.random_bytes())
|
||||
body = self.client.update_image(image_id, data=image_file)['image']
|
||||
self.assertIn('size', body)
|
||||
self.assertEqual(1024, body.get('size'))
|
||||
|
@ -199,7 +199,7 @@ class ListImagesTest(base.BaseV1ImageTest):
|
|||
Note that the size of the new image is a random number between
|
||||
1024 and 4096
|
||||
"""
|
||||
image_file = moves.cStringIO(data_utils.random_bytes(size))
|
||||
image_file = six.BytesIO(data_utils.random_bytes(size))
|
||||
name = 'New Standard Image %s' % name
|
||||
image = cls.create_image(name=name,
|
||||
container_format=container_format,
|
||||
|
@ -294,7 +294,7 @@ class UpdateImageMetaTest(base.BaseV1ImageTest):
|
|||
disk_format, size):
|
||||
"""Create a new standard image and return newly-registered image-id"""
|
||||
|
||||
image_file = moves.cStringIO(data_utils.random_bytes(size))
|
||||
image_file = six.BytesIO(data_utils.random_bytes(size))
|
||||
name = 'New Standard Image %s' % name
|
||||
image = cls.create_image(name=name,
|
||||
container_format=container_format,
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import random
|
||||
|
||||
from six import moves
|
||||
import six
|
||||
|
||||
from oslo_log import log as logging
|
||||
from tempest.api.image import base
|
||||
|
@ -60,7 +60,7 @@ class BasicOperationsImagesTest(base.BaseV2ImageTest):
|
|||
|
||||
# Now try uploading an image file
|
||||
file_content = data_utils.random_bytes()
|
||||
image_file = moves.cStringIO(file_content)
|
||||
image_file = six.BytesIO(file_content)
|
||||
self.client.store_image_file(image_id, image_file)
|
||||
|
||||
# Now try to get image details
|
||||
|
@ -117,7 +117,7 @@ class BasicOperationsImagesTest(base.BaseV2ImageTest):
|
|||
image_id = body['id']
|
||||
|
||||
# Now try uploading an image file
|
||||
image_file = moves.cStringIO(data_utils.random_bytes())
|
||||
image_file = six.BytesIO(data_utils.random_bytes())
|
||||
self.client.store_image_file(image_id, image_file)
|
||||
|
||||
# Update Image
|
||||
|
@ -160,7 +160,7 @@ class ListImagesTest(base.BaseV2ImageTest):
|
|||
1024 and 4096
|
||||
"""
|
||||
size = random.randint(1024, 4096)
|
||||
image_file = moves.cStringIO(data_utils.random_bytes(size))
|
||||
image_file = six.BytesIO(data_utils.random_bytes(size))
|
||||
name = data_utils.rand_name('image')
|
||||
body = cls.create_image(name=name,
|
||||
container_format=container_format,
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
import random
|
||||
|
||||
from six import moves
|
||||
import testtools
|
||||
|
||||
from tempest.api.object_storage import base
|
||||
|
@ -42,7 +41,7 @@ class AccountTest(base.BaseObjectTest):
|
|||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(AccountTest, cls).resource_setup()
|
||||
for i in moves.xrange(ord('a'), ord('f') + 1):
|
||||
for i in range(ord('a'), ord('f') + 1):
|
||||
name = data_utils.rand_name(name='%s-' % chr(i))
|
||||
cls.container_client.create_container(name)
|
||||
cls.containers.append(name)
|
||||
|
@ -140,7 +139,8 @@ class AccountTest(base.BaseObjectTest):
|
|||
@test.idempotent_id('5cfa4ab2-4373-48dd-a41f-a532b12b08b2')
|
||||
def test_list_containers_with_limit(self):
|
||||
# list containers one of them, half of them then all of them
|
||||
for limit in (1, self.containers_count / 2, self.containers_count):
|
||||
for limit in (1, self.containers_count // 2,
|
||||
self.containers_count):
|
||||
params = {'limit': limit}
|
||||
resp, container_list = \
|
||||
self.account_client.list_account_containers(params=params)
|
||||
|
@ -161,12 +161,13 @@ class AccountTest(base.BaseObjectTest):
|
|||
|
||||
self.assertEqual(len(container_list), 0)
|
||||
|
||||
params = {'marker': self.containers[self.containers_count / 2]}
|
||||
params = {'marker': self.containers[self.containers_count // 2]}
|
||||
resp, container_list = \
|
||||
self.account_client.list_account_containers(params=params)
|
||||
self.assertHeaders(resp, 'Account', 'GET')
|
||||
|
||||
self.assertEqual(len(container_list), self.containers_count / 2 - 1)
|
||||
self.assertEqual(len(container_list),
|
||||
self.containers_count // 2 - 1)
|
||||
|
||||
@test.idempotent_id('5ca164e4-7bde-43fa-bafb-913b53b9e786')
|
||||
def test_list_containers_with_end_marker(self):
|
||||
|
@ -180,11 +181,11 @@ class AccountTest(base.BaseObjectTest):
|
|||
self.assertHeaders(resp, 'Account', 'GET')
|
||||
self.assertEqual(len(container_list), 0)
|
||||
|
||||
params = {'end_marker': self.containers[self.containers_count / 2]}
|
||||
params = {'end_marker': self.containers[self.containers_count // 2]}
|
||||
resp, container_list = \
|
||||
self.account_client.list_account_containers(params=params)
|
||||
self.assertHeaders(resp, 'Account', 'GET')
|
||||
self.assertEqual(len(container_list), self.containers_count / 2)
|
||||
self.assertEqual(len(container_list), self.containers_count // 2)
|
||||
|
||||
@test.idempotent_id('ac8502c2-d4e4-4f68-85a6-40befea2ef5e')
|
||||
def test_list_containers_with_marker_and_end_marker(self):
|
||||
|
@ -215,12 +216,12 @@ class AccountTest(base.BaseObjectTest):
|
|||
# list containers combining limit and end_marker param
|
||||
limit = random.randint(1, self.containers_count)
|
||||
params = {'limit': limit,
|
||||
'end_marker': self.containers[self.containers_count / 2]}
|
||||
'end_marker': self.containers[self.containers_count // 2]}
|
||||
resp, container_list = self.account_client.list_account_containers(
|
||||
params=params)
|
||||
self.assertHeaders(resp, 'Account', 'GET')
|
||||
self.assertEqual(len(container_list),
|
||||
min(limit, self.containers_count / 2))
|
||||
min(limit, self.containers_count // 2))
|
||||
|
||||
@test.idempotent_id('8cf98d9c-e3a0-4e44-971b-c87656fdddbd')
|
||||
def test_list_containers_with_limit_and_marker_and_end_marker(self):
|
||||
|
|
|
@ -47,9 +47,9 @@ class ObjectTest(base.BaseObjectTest):
|
|||
object_name = data_utils.rand_name(name='LObject')
|
||||
data = data_utils.arbitrary_string()
|
||||
segments = 10
|
||||
data_segments = [data + str(i) for i in six.moves.xrange(segments)]
|
||||
data_segments = [data + str(i) for i in range(segments)]
|
||||
# uploading segments
|
||||
for i in six.moves.xrange(segments):
|
||||
for i in range(segments):
|
||||
resp, _ = self.object_client.create_object_segments(
|
||||
self.container_name, object_name, i, data_segments[i])
|
||||
|
||||
|
@ -901,9 +901,9 @@ class ObjectTest(base.BaseObjectTest):
|
|||
object_name = data_utils.rand_name(name='LObject')
|
||||
data = data_utils.arbitrary_string()
|
||||
segments = 10
|
||||
data_segments = [data + str(i) for i in six.moves.xrange(segments)]
|
||||
data_segments = [data + str(i) for i in range(segments)]
|
||||
# uploading segments
|
||||
for i in six.moves.xrange(segments):
|
||||
for i in range(segments):
|
||||
resp, _ = self.object_client.create_object_segments(
|
||||
self.container_name, object_name, i, data_segments[i])
|
||||
# creating a manifest file
|
||||
|
|
|
@ -13,9 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import base64
|
||||
import six
|
||||
|
||||
from oslo_serialization import base64
|
||||
from oslo_serialization import jsonutils as json
|
||||
|
||||
from tempest.api.volume import base
|
||||
|
@ -46,13 +44,11 @@ class VolumesBackupsAdminV2Test(base.BaseVolumeAdminTest):
|
|||
self.admin_backups_client.wait_for_resource_deletion(backup_id)
|
||||
|
||||
def _decode_url(self, backup_url):
|
||||
return json.loads(base64.decodestring(backup_url))
|
||||
return json.loads(base64.decode_as_text(backup_url))
|
||||
|
||||
def _encode_backup(self, backup):
|
||||
retval = json.dumps(backup)
|
||||
if six.PY3:
|
||||
retval = retval.encode('utf-8')
|
||||
return base64.encodestring(retval)
|
||||
return base64.encode_as_text(retval)
|
||||
|
||||
def _modify_backup_url(self, backup_url, changes):
|
||||
backup = self._decode_url(backup_url)
|
||||
|
|
|
@ -36,7 +36,7 @@ class VolumesV2SnapshotTestJSON(base.BaseVolumeTest):
|
|||
cls.name_field = cls.special_fields['name_field']
|
||||
cls.descrip_field = cls.special_fields['descrip_field']
|
||||
# Create 2 snapshots
|
||||
for _ in xrange(2):
|
||||
for _ in range(2):
|
||||
cls.create_snapshot(cls.volume_origin['id'])
|
||||
|
||||
def _detach(self, volume_id):
|
||||
|
|
|
@ -46,7 +46,7 @@ def _get_config_file():
|
|||
conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', default_config_dir)
|
||||
conf_file = os.environ.get('TEMPEST_CONFIG', default_config_file)
|
||||
path = os.path.join(conf_dir, conf_file)
|
||||
fd = open(path, 'rw')
|
||||
fd = open(path, 'r+')
|
||||
return fd
|
||||
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ def random_bytes(size=1024):
|
|||
:return: size randomly bytes
|
||||
:rtype: string
|
||||
"""
|
||||
return ''.join([chr(random.randint(0, 255))
|
||||
return b''.join([six.int2byte(random.randint(0, 255))
|
||||
for i in range(size)])
|
||||
|
||||
|
||||
|
@ -204,5 +204,5 @@ def get_ipv6_addr_by_EUI64(cidr, mac):
|
|||
# Courtesy of http://stackoverflow.com/a/312464
|
||||
def chunkify(sequence, chunksize):
|
||||
"""Yield successive chunks from `sequence`."""
|
||||
for i in six.moves.xrange(0, len(sequence), chunksize):
|
||||
for i in range(0, len(sequence), chunksize):
|
||||
yield sequence[i:i + chunksize]
|
||||
|
|
|
@ -195,7 +195,6 @@ class ObjectClient(rest_client.RestClient):
|
|||
|
||||
# Read the 100 status prior to sending the data
|
||||
response = conn.response_class(conn.sock,
|
||||
strict=conn.strict,
|
||||
method=conn._method)
|
||||
_, status, _ = response._read_status()
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ import time
|
|||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
import six
|
||||
from six import moves
|
||||
|
||||
|
||||
from tempest import clients
|
||||
from tempest.common import cred_client
|
||||
|
@ -142,7 +140,7 @@ def stress_openstack(tests, duration, max_runs=None, stop_on_error=False):
|
|||
manager = admin_manager
|
||||
else:
|
||||
raise NotImplemented('Non admin tests are not supported')
|
||||
for p_number in moves.xrange(test.get('threads', default_thread_num)):
|
||||
for p_number in range(test.get('threads', default_thread_num)):
|
||||
if test.get('use_isolated_tenants', False):
|
||||
username = data_utils.rand_name("stress_user")
|
||||
tenant_name = data_utils.rand_name("stress_tenant")
|
||||
|
|
|
@ -125,13 +125,13 @@ class TestDataUtils(base.TestCase):
|
|||
|
||||
def test_random_bytes(self):
|
||||
actual = data_utils.random_bytes() # default size=1024
|
||||
self.assertIsInstance(actual, str)
|
||||
self.assertRegex(actual, "^[\x00-\xFF]{1024}")
|
||||
self.assertIsInstance(actual, bytes)
|
||||
self.assertEqual(1024, len(actual))
|
||||
actual2 = data_utils.random_bytes()
|
||||
self.assertNotEqual(actual, actual2)
|
||||
|
||||
actual = data_utils.random_bytes(size=2048)
|
||||
self.assertRegex(actual, "^[\x00-\xFF]{2048}")
|
||||
self.assertEqual(2048, len(actual))
|
||||
|
||||
def test_get_ipv6_addr_by_EUI64(self):
|
||||
actual = data_utils.get_ipv6_addr_by_EUI64('2001:db8::',
|
||||
|
|
Loading…
Reference in New Issue