Merge "object-ify flavors manager side of the RPC"
This commit is contained in:
commit
16d38947ae
|
@ -73,7 +73,7 @@ class CellsManager(manager.Manager):
|
||||||
Scheduling requests get passed to the scheduler class.
|
Scheduling requests get passed to the scheduler class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
target = oslo_messaging.Target(version='1.29')
|
target = oslo_messaging.Target(version='1.30')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
LOG.warning(_LW('The cells feature of Nova is considered experimental '
|
LOG.warning(_LW('The cells feature of Nova is considered experimental '
|
||||||
|
|
|
@ -101,6 +101,8 @@ class CellsAPI(object):
|
||||||
... Juno supports message version 1.29. So, any changes to
|
... Juno supports message version 1.29. So, any changes to
|
||||||
existing methods in 1.x after that point should be done such that they
|
existing methods in 1.x after that point should be done such that they
|
||||||
can handle the version_cap being set to 1.29.
|
can handle the version_cap being set to 1.29.
|
||||||
|
|
||||||
|
* 1.30 - Make build_instances() use flavor object
|
||||||
'''
|
'''
|
||||||
|
|
||||||
VERSION_ALIASES = {
|
VERSION_ALIASES = {
|
||||||
|
@ -150,11 +152,15 @@ class CellsAPI(object):
|
||||||
build_inst_kwargs['instances'] = instances_p
|
build_inst_kwargs['instances'] = instances_p
|
||||||
build_inst_kwargs['image'] = jsonutils.to_primitive(
|
build_inst_kwargs['image'] = jsonutils.to_primitive(
|
||||||
build_inst_kwargs['image'])
|
build_inst_kwargs['image'])
|
||||||
if 'filter_properties' in build_inst_kwargs:
|
version = '1.30'
|
||||||
flavor = build_inst_kwargs['filter_properties']['instance_type']
|
if not self.client.can_send_version(version):
|
||||||
flavor_p = objects_base.obj_to_primitive(flavor)
|
version = '1.8'
|
||||||
build_inst_kwargs['filter_properties']['instance_type'] = flavor_p
|
if 'filter_properties' in build_inst_kwargs:
|
||||||
cctxt = self.client.prepare(version='1.8')
|
filter_properties = build_inst_kwargs['filter_properties']
|
||||||
|
flavor = filter_properties['instance_type']
|
||||||
|
flavor_p = objects_base.obj_to_primitive(flavor)
|
||||||
|
filter_properties['instance_type'] = flavor_p
|
||||||
|
cctxt = self.client.prepare(version=version)
|
||||||
cctxt.cast(ctxt, 'build_instances',
|
cctxt.cast(ctxt, 'build_instances',
|
||||||
build_inst_kwargs=build_inst_kwargs)
|
build_inst_kwargs=build_inst_kwargs)
|
||||||
|
|
||||||
|
|
|
@ -587,7 +587,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
|
||||||
class ComputeManager(manager.Manager):
|
class ComputeManager(manager.Manager):
|
||||||
"""Manages the running instances from creation to destruction."""
|
"""Manages the running instances from creation to destruction."""
|
||||||
|
|
||||||
target = messaging.Target(version='3.35')
|
target = messaging.Target(version='3.36')
|
||||||
|
|
||||||
# How long to wait in seconds before re-issuing a shutdown
|
# How long to wait in seconds before re-issuing a shutdown
|
||||||
# signal to a instance during power off. The overall
|
# signal to a instance during power off. The overall
|
||||||
|
@ -1994,6 +1994,13 @@ class ComputeManager(manager.Manager):
|
||||||
requested_networks = objects.NetworkRequestList(
|
requested_networks = objects.NetworkRequestList(
|
||||||
objects=[objects.NetworkRequest.from_tuple(t)
|
objects=[objects.NetworkRequest.from_tuple(t)
|
||||||
for t in requested_networks])
|
for t in requested_networks])
|
||||||
|
# NOTE(melwitt): Remove this in v4.0 of the RPC API
|
||||||
|
flavor = filter_properties.get('instance_type')
|
||||||
|
if flavor and not isinstance(flavor, objects.Flavor):
|
||||||
|
# Code downstream may expect extra_specs to be populated since it
|
||||||
|
# is receiving an object, so lookup the flavor to ensure this.
|
||||||
|
flavor = objects.Flavor.get_by_id(context, flavor['id'])
|
||||||
|
filter_properties = dict(filter_properties, instance_type=flavor)
|
||||||
|
|
||||||
@utils.synchronized(instance.uuid)
|
@utils.synchronized(instance.uuid)
|
||||||
def _locked_do_build_and_run_instance(*args, **kwargs):
|
def _locked_do_build_and_run_instance(*args, **kwargs):
|
||||||
|
|
|
@ -274,6 +274,8 @@ class ComputeAPI(object):
|
||||||
... Juno supports message version 3.35. So, any changes to
|
... Juno supports message version 3.35. So, any changes to
|
||||||
existing methods in 3.x after that point should be done such that they
|
existing methods in 3.x after that point should be done such that they
|
||||||
can handle the version_cap being set to 3.35.
|
can handle the version_cap being set to 3.35.
|
||||||
|
|
||||||
|
* 3.36 - Make build_and_run_instance() send a Flavor object
|
||||||
'''
|
'''
|
||||||
|
|
||||||
VERSION_ALIASES = {
|
VERSION_ALIASES = {
|
||||||
|
@ -880,17 +882,20 @@ class ComputeAPI(object):
|
||||||
filter_properties, admin_password=None, injected_files=None,
|
filter_properties, admin_password=None, injected_files=None,
|
||||||
requested_networks=None, security_groups=None,
|
requested_networks=None, security_groups=None,
|
||||||
block_device_mapping=None, node=None, limits=None):
|
block_device_mapping=None, node=None, limits=None):
|
||||||
version = '3.33'
|
version = '3.36'
|
||||||
|
if not self.client.can_send_version(version):
|
||||||
|
version = '3.33'
|
||||||
|
if 'instance_type' in filter_properties:
|
||||||
|
flavor = filter_properties['instance_type']
|
||||||
|
flavor_p = objects_base.obj_to_primitive(flavor)
|
||||||
|
filter_properties = dict(filter_properties,
|
||||||
|
instance_type=flavor_p)
|
||||||
if not self.client.can_send_version(version):
|
if not self.client.can_send_version(version):
|
||||||
version = '3.23'
|
version = '3.23'
|
||||||
if requested_networks is not None:
|
if requested_networks is not None:
|
||||||
requested_networks = [(network_id, address, port_id)
|
requested_networks = [(network_id, address, port_id)
|
||||||
for (network_id, address, port_id, _) in
|
for (network_id, address, port_id, _) in
|
||||||
requested_networks.as_tuples()]
|
requested_networks.as_tuples()]
|
||||||
if 'instance_type' in filter_properties:
|
|
||||||
flavor = filter_properties['instance_type']
|
|
||||||
flavor_p = objects_base.obj_to_primitive(flavor)
|
|
||||||
filter_properties = dict(filter_properties, instance_type=flavor_p)
|
|
||||||
|
|
||||||
cctxt = self.client.prepare(server=host, version=version)
|
cctxt = self.client.prepare(server=host, version=version)
|
||||||
cctxt.cast(ctxt, 'build_and_run_instance', instance=instance,
|
cctxt.cast(ctxt, 'build_and_run_instance', instance=instance,
|
||||||
|
|
|
@ -461,7 +461,7 @@ class ComputeTaskManager(base.Base):
|
||||||
may involve coordinating activities on multiple compute nodes.
|
may involve coordinating activities on multiple compute nodes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
target = messaging.Target(namespace='compute_task', version='1.9')
|
target = messaging.Target(namespace='compute_task', version='1.10')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ComputeTaskManager, self).__init__()
|
super(ComputeTaskManager, self).__init__()
|
||||||
|
@ -491,6 +491,11 @@ class ComputeTaskManager(base.Base):
|
||||||
instance = objects.Instance._from_db_object(
|
instance = objects.Instance._from_db_object(
|
||||||
context, objects.Instance(), instance,
|
context, objects.Instance(), instance,
|
||||||
expected_attrs=attrs)
|
expected_attrs=attrs)
|
||||||
|
# NOTE(melwitt): Remove this in version 2.0 of the RPC API
|
||||||
|
if flavor and not isinstance(flavor, objects.Flavor):
|
||||||
|
# Code downstream may expect extra_specs to be populated since it
|
||||||
|
# is receiving an object, so lookup the flavor to ensure this.
|
||||||
|
flavor = objects.Flavor.get_by_id(context, flavor['id'])
|
||||||
if live and not rebuild and not flavor:
|
if live and not rebuild and not flavor:
|
||||||
self._live_migrate(context, instance, scheduler_hint,
|
self._live_migrate(context, instance, scheduler_hint,
|
||||||
block_migration, disk_over_commit)
|
block_migration, disk_over_commit)
|
||||||
|
@ -545,11 +550,6 @@ class ComputeTaskManager(base.Base):
|
||||||
# context is not serializable
|
# context is not serializable
|
||||||
filter_properties.pop('context', None)
|
filter_properties.pop('context', None)
|
||||||
|
|
||||||
# TODO(timello): originally, instance_type in request_spec
|
|
||||||
# on compute.api.resize does not have 'extra_specs', so we
|
|
||||||
# remove it for now to keep tests backward compatibility.
|
|
||||||
request_spec['instance_type'].pop('extra_specs', None)
|
|
||||||
|
|
||||||
(host, node) = (host_state['host'], host_state['nodename'])
|
(host, node) = (host_state['host'], host_state['nodename'])
|
||||||
self.compute_rpcapi.prep_resize(
|
self.compute_rpcapi.prep_resize(
|
||||||
context, image, instance,
|
context, image, instance,
|
||||||
|
@ -622,6 +622,13 @@ class ComputeTaskManager(base.Base):
|
||||||
requested_networks = objects.NetworkRequestList(
|
requested_networks = objects.NetworkRequestList(
|
||||||
objects=[objects.NetworkRequest.from_tuple(t)
|
objects=[objects.NetworkRequest.from_tuple(t)
|
||||||
for t in requested_networks])
|
for t in requested_networks])
|
||||||
|
# TODO(melwitt): Remove this in version 2.0 of the RPC API
|
||||||
|
flavor = filter_properties.get('instance_type')
|
||||||
|
if flavor and not isinstance(flavor, objects.Flavor):
|
||||||
|
# Code downstream may expect extra_specs to be populated since it
|
||||||
|
# is receiving an object, so lookup the flavor to ensure this.
|
||||||
|
flavor = objects.Flavor.get_by_id(context, flavor['id'])
|
||||||
|
filter_properties = dict(filter_properties, instance_type=flavor)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# check retry policy. Rather ugly use of instances[0]...
|
# check retry policy. Rather ugly use of instances[0]...
|
||||||
|
|
|
@ -390,6 +390,7 @@ class ComputeTaskAPI(object):
|
||||||
1.7 - Do not send block_device_mapping and legacy_bdm to build_instances
|
1.7 - Do not send block_device_mapping and legacy_bdm to build_instances
|
||||||
1.8 - Add rebuild_instance
|
1.8 - Add rebuild_instance
|
||||||
1.9 - Converted requested_networks to NetworkRequestList object
|
1.9 - Converted requested_networks to NetworkRequestList object
|
||||||
|
1.10 - Made migrate_server() and build_instances() send flavor objects
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -404,17 +405,18 @@ class ComputeTaskAPI(object):
|
||||||
def migrate_server(self, context, instance, scheduler_hint, live, rebuild,
|
def migrate_server(self, context, instance, scheduler_hint, live, rebuild,
|
||||||
flavor, block_migration, disk_over_commit,
|
flavor, block_migration, disk_over_commit,
|
||||||
reservations=None):
|
reservations=None):
|
||||||
if self.client.can_send_version('1.6'):
|
version = '1.10'
|
||||||
|
if not self.client.can_send_version(version):
|
||||||
|
flavor = objects_base.obj_to_primitive(flavor)
|
||||||
version = '1.6'
|
version = '1.6'
|
||||||
else:
|
if not self.client.can_send_version(version):
|
||||||
instance = jsonutils.to_primitive(
|
instance = jsonutils.to_primitive(
|
||||||
objects_base.obj_to_primitive(instance))
|
objects_base.obj_to_primitive(instance))
|
||||||
version = '1.4'
|
version = '1.4'
|
||||||
flavor_p = jsonutils.to_primitive(flavor)
|
|
||||||
cctxt = self.client.prepare(version=version)
|
cctxt = self.client.prepare(version=version)
|
||||||
return cctxt.call(context, 'migrate_server',
|
return cctxt.call(context, 'migrate_server',
|
||||||
instance=instance, scheduler_hint=scheduler_hint,
|
instance=instance, scheduler_hint=scheduler_hint,
|
||||||
live=live, rebuild=rebuild, flavor=flavor_p,
|
live=live, rebuild=rebuild, flavor=flavor,
|
||||||
block_migration=block_migration,
|
block_migration=block_migration,
|
||||||
disk_over_commit=disk_over_commit,
|
disk_over_commit=disk_over_commit,
|
||||||
reservations=reservations)
|
reservations=reservations)
|
||||||
|
@ -423,19 +425,21 @@ class ComputeTaskAPI(object):
|
||||||
admin_password, injected_files, requested_networks,
|
admin_password, injected_files, requested_networks,
|
||||||
security_groups, block_device_mapping, legacy_bdm=True):
|
security_groups, block_device_mapping, legacy_bdm=True):
|
||||||
image_p = jsonutils.to_primitive(image)
|
image_p = jsonutils.to_primitive(image)
|
||||||
if 'instance_type' in filter_properties:
|
version = '1.10'
|
||||||
flavor = filter_properties['instance_type']
|
if not self.client.can_send_version(version):
|
||||||
flavor_p = objects_base.obj_to_primitive(flavor)
|
version = '1.9'
|
||||||
filter_properties = dict(filter_properties, instance_type=flavor_p)
|
if 'instance_type' in filter_properties:
|
||||||
|
flavor = filter_properties['instance_type']
|
||||||
|
flavor_p = objects_base.obj_to_primitive(flavor)
|
||||||
|
filter_properties = dict(filter_properties,
|
||||||
|
instance_type=flavor_p)
|
||||||
kw = {'instances': instances, 'image': image_p,
|
kw = {'instances': instances, 'image': image_p,
|
||||||
'filter_properties': filter_properties,
|
'filter_properties': filter_properties,
|
||||||
'admin_password': admin_password,
|
'admin_password': admin_password,
|
||||||
'injected_files': injected_files,
|
'injected_files': injected_files,
|
||||||
'requested_networks': requested_networks,
|
'requested_networks': requested_networks,
|
||||||
'security_groups': security_groups}
|
'security_groups': security_groups}
|
||||||
|
if not self.client.can_send_version(version):
|
||||||
version = '1.9'
|
|
||||||
if not self.client.can_send_version('1.9'):
|
|
||||||
version = '1.8'
|
version = '1.8'
|
||||||
kw['requested_networks'] = kw['requested_networks'].as_tuples()
|
kw['requested_networks'] = kw['requested_networks'].as_tuples()
|
||||||
if not self.client.can_send_version('1.7'):
|
if not self.client.can_send_version('1.7'):
|
||||||
|
|
|
@ -60,7 +60,7 @@ QUOTAS = quota.QUOTAS
|
||||||
class SchedulerManager(manager.Manager):
|
class SchedulerManager(manager.Manager):
|
||||||
"""Chooses a host to run instances on."""
|
"""Chooses a host to run instances on."""
|
||||||
|
|
||||||
target = messaging.Target(version='3.0')
|
target = messaging.Target(version='3.1')
|
||||||
|
|
||||||
def __init__(self, scheduler_driver=None, *args, **kwargs):
|
def __init__(self, scheduler_driver=None, *args, **kwargs):
|
||||||
if not scheduler_driver:
|
if not scheduler_driver:
|
||||||
|
@ -171,6 +171,13 @@ class SchedulerManager(manager.Manager):
|
||||||
The result should be a list of dicts with 'host', 'nodename' and
|
The result should be a list of dicts with 'host', 'nodename' and
|
||||||
'limits' as keys.
|
'limits' as keys.
|
||||||
"""
|
"""
|
||||||
|
# TODO(melwitt): Remove this in version 4.0 of the RPC API
|
||||||
|
flavor = filter_properties.get('instance_type')
|
||||||
|
if flavor and not isinstance(flavor, objects.Flavor):
|
||||||
|
# Code downstream may expect extra_specs to be populated since it
|
||||||
|
# is receiving an object, so lookup the flavor to ensure this.
|
||||||
|
flavor = objects.Flavor.get_by_id(context, flavor['id'])
|
||||||
|
filter_properties = dict(filter_properties, instance_type=flavor)
|
||||||
dests = self.driver.select_destinations(context, request_spec,
|
dests = self.driver.select_destinations(context, request_spec,
|
||||||
filter_properties)
|
filter_properties)
|
||||||
return jsonutils.to_primitive(dests)
|
return jsonutils.to_primitive(dests)
|
||||||
|
|
|
@ -84,6 +84,8 @@ class SchedulerAPI(object):
|
||||||
existing methods in 3.x after that point should be done such that they
|
existing methods in 3.x after that point should be done such that they
|
||||||
can handle the version_cap being set to 3.0.
|
can handle the version_cap being set to 3.0.
|
||||||
|
|
||||||
|
* 3.1 - Made select_destinations() send flavor object
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
VERSION_ALIASES = {
|
VERSION_ALIASES = {
|
||||||
|
@ -103,10 +105,14 @@ class SchedulerAPI(object):
|
||||||
serializer=serializer)
|
serializer=serializer)
|
||||||
|
|
||||||
def select_destinations(self, ctxt, request_spec, filter_properties):
|
def select_destinations(self, ctxt, request_spec, filter_properties):
|
||||||
if 'instance_type' in filter_properties:
|
version = '3.1'
|
||||||
flavor = filter_properties['instance_type']
|
if not self.client.can_send_version(version):
|
||||||
flavor_p = objects_base.obj_to_primitive(flavor)
|
version = '3.0'
|
||||||
filter_properties = dict(filter_properties, instance_type=flavor_p)
|
if 'instance_type' in filter_properties:
|
||||||
cctxt = self.client.prepare()
|
flavor = filter_properties['instance_type']
|
||||||
|
flavor_p = objects_base.obj_to_primitive(flavor)
|
||||||
|
filter_properties = dict(filter_properties,
|
||||||
|
instance_type=flavor_p)
|
||||||
|
cctxt = self.client.prepare(version=version)
|
||||||
return cctxt.call(ctxt, 'select_destinations',
|
return cctxt.call(ctxt, 'select_destinations',
|
||||||
request_spec=request_spec, filter_properties=filter_properties)
|
request_spec=request_spec, filter_properties=filter_properties)
|
||||||
|
|
|
@ -135,7 +135,7 @@ class CellsAPITestCase(test.NoDBTestCase):
|
||||||
'arg2': 2,
|
'arg2': 2,
|
||||||
'arg3': 3}}
|
'arg3': 3}}
|
||||||
self._check_result(call_info, 'build_instances',
|
self._check_result(call_info, 'build_instances',
|
||||||
expected_args, version='1.8')
|
expected_args, version='1.30')
|
||||||
|
|
||||||
def test_get_capacities(self):
|
def test_get_capacities(self):
|
||||||
capacity_info = {"capacity": "info"}
|
capacity_info = {"capacity": "info"}
|
||||||
|
|
|
@ -468,7 +468,7 @@ class ComputeRpcAPITestCase(test.TestCase):
|
||||||
admin_password='passwd', injected_files=None,
|
admin_password='passwd', injected_files=None,
|
||||||
requested_networks=['network1'], security_groups=None,
|
requested_networks=['network1'], security_groups=None,
|
||||||
block_device_mapping=None, node='node', limits=[],
|
block_device_mapping=None, node='node', limits=[],
|
||||||
version='3.33')
|
version='3.36')
|
||||||
|
|
||||||
@mock.patch('nova.utils.is_neutron', return_value=True)
|
@mock.patch('nova.utils.is_neutron', return_value=True)
|
||||||
def test_build_and_run_instance_icehouse_compat(self, is_neutron):
|
def test_build_and_run_instance_icehouse_compat(self, is_neutron):
|
||||||
|
|
|
@ -1196,9 +1196,9 @@ class _BaseTaskTestCase(object):
|
||||||
inst = fake_instance.fake_db_instance(image_ref='image_ref')
|
inst = fake_instance.fake_db_instance(image_ref='image_ref')
|
||||||
inst_obj = objects.Instance._from_db_object(
|
inst_obj = objects.Instance._from_db_object(
|
||||||
self.context, objects.Instance(), inst, [])
|
self.context, objects.Instance(), inst, [])
|
||||||
flavor = obj_base.obj_to_primitive(flavors.get_default_flavor())
|
flavor = flavors.get_default_flavor()
|
||||||
flavor['extra_specs'] = 'extra_specs'
|
flavor.extra_specs = {'extra_specs': 'fake'}
|
||||||
request_spec = {'instance_type': flavor,
|
request_spec = {'instance_type': obj_base.obj_to_primitive(flavor),
|
||||||
'instance_properties': {}}
|
'instance_properties': {}}
|
||||||
compute_utils.get_image_metadata(
|
compute_utils.get_image_metadata(
|
||||||
self.context, self.conductor_manager.image_api,
|
self.context, self.conductor_manager.image_api,
|
||||||
|
@ -1207,7 +1207,7 @@ class _BaseTaskTestCase(object):
|
||||||
scheduler_utils.build_request_spec(
|
scheduler_utils.build_request_spec(
|
||||||
self.context, 'image',
|
self.context, 'image',
|
||||||
[mox.IsA(objects.Instance)],
|
[mox.IsA(objects.Instance)],
|
||||||
instance_type=flavor).AndReturn(request_spec)
|
instance_type=mox.IsA(objects.Flavor)).AndReturn(request_spec)
|
||||||
|
|
||||||
scheduler_utils.setup_instance_group(self.context, request_spec, {})
|
scheduler_utils.setup_instance_group(self.context, request_spec, {})
|
||||||
|
|
||||||
|
@ -1222,7 +1222,7 @@ class _BaseTaskTestCase(object):
|
||||||
|
|
||||||
self.conductor_manager.compute_rpcapi.prep_resize(
|
self.conductor_manager.compute_rpcapi.prep_resize(
|
||||||
self.context, 'image', mox.IsA(objects.Instance),
|
self.context, 'image', mox.IsA(objects.Instance),
|
||||||
mox.IsA(dict), 'host1', [], request_spec=request_spec,
|
mox.IsA(objects.Flavor), 'host1', [], request_spec=request_spec,
|
||||||
filter_properties=filter_properties, node=None)
|
filter_properties=filter_properties, node=None)
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
@ -1630,8 +1630,9 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
|
||||||
self.context, None, None, True, True, None, None, None)
|
self.context, None, None, True, True, None, None, None)
|
||||||
|
|
||||||
def test_migrate_server_fails_with_flavor(self):
|
def test_migrate_server_fails_with_flavor(self):
|
||||||
|
flavor = flavors.get_flavor_by_name('m1.tiny')
|
||||||
self.assertRaises(NotImplementedError, self.conductor.migrate_server,
|
self.assertRaises(NotImplementedError, self.conductor.migrate_server,
|
||||||
self.context, None, None, True, False, "dummy", None, None)
|
self.context, None, None, True, False, flavor, None, None)
|
||||||
|
|
||||||
def _build_request_spec(self, instance):
|
def _build_request_spec(self, instance):
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -66,4 +66,5 @@ class SchedulerRpcAPITestCase(test.NoDBTestCase):
|
||||||
def test_select_destinations(self):
|
def test_select_destinations(self):
|
||||||
self._test_scheduler_api('select_destinations', rpc_method='call',
|
self._test_scheduler_api('select_destinations', rpc_method='call',
|
||||||
request_spec='fake_request_spec',
|
request_spec='fake_request_spec',
|
||||||
filter_properties='fake_prop')
|
filter_properties='fake_prop',
|
||||||
|
version='3.1')
|
||||||
|
|
Loading…
Reference in New Issue