Create trust_id for bay
All bays use the same trustee_user and different trust. A trust is created for a bay when the bay is created, and is deleted when the bay is deleted. Partially-Implements: blueprint registryv2-in-master Change-Id: Iab2037677f683fe4c562915b98303da02c59c299
This commit is contained in:
parent
554af35b74
commit
8074f6f4ce
|
@ -142,6 +142,10 @@
|
||||||
# (string value)
|
# (string value)
|
||||||
#instance_uuid_format = "[instance: %(uuid)s] "
|
#instance_uuid_format = "[instance: %(uuid)s] "
|
||||||
|
|
||||||
|
# Format string for user_identity field of the
|
||||||
|
# logging_context_format_string (string value)
|
||||||
|
#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
|
||||||
|
|
||||||
# Enables or disables fatal status of deprecations. (boolean value)
|
# Enables or disables fatal status of deprecations. (boolean value)
|
||||||
#fatal_deprecations = false
|
#fatal_deprecations = false
|
||||||
|
|
||||||
|
@ -194,6 +198,16 @@
|
||||||
# Shows whether zmq-messaging uses broker or not. (boolean value)
|
# Shows whether zmq-messaging uses broker or not. (boolean value)
|
||||||
#zmq_use_broker = true
|
#zmq_use_broker = true
|
||||||
|
|
||||||
|
# Minimal port number for random ports range. (integer value)
|
||||||
|
#rpc_zmq_min_port = 49152
|
||||||
|
|
||||||
|
# Maximal port number for random ports range. (integer value)
|
||||||
|
#rpc_zmq_max_port = 65536
|
||||||
|
|
||||||
|
# Number of retries to find free port number before fail with
|
||||||
|
# ZMQBindError. (integer value)
|
||||||
|
#rpc_zmq_bind_port_retries = 100
|
||||||
|
|
||||||
# Host to locate redis. (string value)
|
# Host to locate redis. (string value)
|
||||||
#host = 127.0.0.1
|
#host = 127.0.0.1
|
||||||
|
|
||||||
|
@ -211,6 +225,11 @@
|
||||||
# messaging, messagingv2, routing, log, test, noop (multi valued)
|
# messaging, messagingv2, routing, log, test, noop (multi valued)
|
||||||
#notification_driver =
|
#notification_driver =
|
||||||
|
|
||||||
|
# A URL representing the messaging driver to use for notifications. If
|
||||||
|
# not set, we fall back to the same configuration used for RPC.
|
||||||
|
# (string value)
|
||||||
|
#notification_transport_url = <None>
|
||||||
|
|
||||||
# AMQP topic used for OpenStack notifications. (list value)
|
# AMQP topic used for OpenStack notifications. (list value)
|
||||||
# Deprecated group/name - [rpc_notifier2]/topics
|
# Deprecated group/name - [rpc_notifier2]/topics
|
||||||
#notification_topics = notifications
|
#notification_topics = notifications
|
||||||
|
@ -259,7 +278,7 @@
|
||||||
|
|
||||||
# Specify a timeout after which a gracefully shutdown server will
|
# Specify a timeout after which a gracefully shutdown server will
|
||||||
# exit. Zero value means endless wait. (integer value)
|
# exit. Zero value means endless wait. (integer value)
|
||||||
#graceful_shutdown_timeout = 0
|
#graceful_shutdown_timeout = 60
|
||||||
|
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
|
@ -320,7 +339,7 @@
|
||||||
|
|
||||||
# Location of template to build a swarm cluster on atomic. (string
|
# Location of template to build a swarm cluster on atomic. (string
|
||||||
# value)
|
# value)
|
||||||
#swarm_atomic_template_path = $pybasedir/templates/swarm/swarm.yaml
|
#swarm_atomic_template_path = $pybasedir/templates/swarm/swarmcluster.yaml
|
||||||
|
|
||||||
# Location of template to build a Mesos cluster on Ubuntu. (string
|
# Location of template to build a Mesos cluster on Ubuntu. (string
|
||||||
# value)
|
# value)
|
||||||
|
@ -363,23 +382,23 @@
|
||||||
# network drivers include flannel. (list value)
|
# network drivers include flannel. (list value)
|
||||||
#kubernetes_allowed_network_drivers = all
|
#kubernetes_allowed_network_drivers = all
|
||||||
|
|
||||||
# Default network driver for kubernetes baymodels.
|
# Default network driver for kubernetes baymodels. (string value)
|
||||||
#kubernetes_default_network_driver = flannel
|
#kubernetes_default_network_driver = flannel
|
||||||
|
|
||||||
# Allowed network drivers for docker swarm baymodels. Use 'all' keyword
|
# Allowed network drivers for docker swarm baymodels. Use 'all'
|
||||||
# to allow all drivers supported for swarm baymodels. Supported
|
# keyword to allow all drivers supported for swarm baymodels.
|
||||||
# network drivers include docker. (list value)
|
# Supported network drivers include docker and flannel. (list value)
|
||||||
#swarm_allowed_network_drivers = all
|
#swarm_allowed_network_drivers = all
|
||||||
|
|
||||||
# Default network driver for docker swarm baymodels.
|
# Default network driver for docker swarm baymodels. (string value)
|
||||||
#swarm_default_network_driver = docker
|
#swarm_default_network_driver = docker
|
||||||
|
|
||||||
# Allowed network drivers for mesos baymodels. Use 'all' keyword
|
# Allowed network drivers for mesos baymodels. Use 'all' keyword to
|
||||||
# to allow all drivers supported for mesos baymodels. Supported
|
# allow all drivers supported for mesos baymodels. Supported network
|
||||||
# network drivers include docker. (list value)
|
# drivers include docker. (list value)
|
||||||
#mesos_allowed_network_drivers = all
|
#mesos_allowed_network_drivers = all
|
||||||
|
|
||||||
# Default network driver for mesos baymodels.
|
# Default network driver for mesos baymodels. (string value)
|
||||||
#mesos_default_network_driver = docker
|
#mesos_default_network_driver = docker
|
||||||
|
|
||||||
|
|
||||||
|
@ -418,7 +437,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# Indicate whether this resource may be shared with the domain
|
# Indicate whether this resource may be shared with the domain
|
||||||
# received in the requests "origin" header. (string value)
|
# received in the requests "origin" header. (list value)
|
||||||
#allowed_origin = <None>
|
#allowed_origin = <None>
|
||||||
|
|
||||||
# Indicate that the actual request can include user credentials
|
# Indicate that the actual request can include user credentials
|
||||||
|
@ -448,7 +467,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# Indicate whether this resource may be shared with the domain
|
# Indicate whether this resource may be shared with the domain
|
||||||
# received in the requests "origin" header. (string value)
|
# received in the requests "origin" header. (list value)
|
||||||
#allowed_origin = <None>
|
#allowed_origin = <None>
|
||||||
|
|
||||||
# Indicate that the actual request can include user credentials
|
# Indicate that the actual request can include user credentials
|
||||||
|
@ -596,7 +615,7 @@
|
||||||
|
|
||||||
# Default timeout in seconds for docker client operations. (integer
|
# Default timeout in seconds for docker client operations. (integer
|
||||||
# value)
|
# value)
|
||||||
#default_timeout = 10
|
#default_timeout = 60
|
||||||
|
|
||||||
# If set, ignore any SSL validation issues (boolean value)
|
# If set, ignore any SSL validation issues (boolean value)
|
||||||
#api_insecure = false
|
#api_insecure = false
|
||||||
|
@ -614,6 +633,20 @@
|
||||||
#key_file = <None>
|
#key_file = <None>
|
||||||
|
|
||||||
|
|
||||||
|
[docker_registry]
|
||||||
|
|
||||||
|
#
|
||||||
|
# From magnum
|
||||||
|
#
|
||||||
|
|
||||||
|
# User id of the trustee (string value)
|
||||||
|
#trustee_user_id = <None>
|
||||||
|
|
||||||
|
# The roles which are delegated to the trustee by the trustor. (list
|
||||||
|
# value)
|
||||||
|
#trust_roles = registry_user
|
||||||
|
|
||||||
|
|
||||||
[glance_client]
|
[glance_client]
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -829,6 +862,14 @@
|
||||||
# Service tenant name. (string value)
|
# Service tenant name. (string value)
|
||||||
#admin_tenant_name = admin
|
#admin_tenant_name = admin
|
||||||
|
|
||||||
|
# Authentication type to load (unknown value)
|
||||||
|
# Deprecated group/name - [DEFAULT]/auth_plugin
|
||||||
|
#auth_type = <None>
|
||||||
|
|
||||||
|
# Config Section from which to load plugin specific options (unknown
|
||||||
|
# value)
|
||||||
|
#auth_section = <None>
|
||||||
|
|
||||||
|
|
||||||
[magnum_client]
|
[magnum_client]
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@ class BayPatchType(types.JsonPatchType):
|
||||||
def internal_attrs():
|
def internal_attrs():
|
||||||
internal_attrs = ['/api_address', '/node_addresses',
|
internal_attrs = ['/api_address', '/node_addresses',
|
||||||
'/master_addresses', '/stack_id',
|
'/master_addresses', '/stack_id',
|
||||||
'/ca_cert_ref', '/magnum_cert_ref']
|
'/ca_cert_ref', '/magnum_cert_ref',
|
||||||
|
'/registry_trust_id']
|
||||||
return types.JsonPatchType.internal_attrs() + internal_attrs
|
return types.JsonPatchType.internal_attrs() + internal_attrs
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -540,3 +540,11 @@ class FlavorNotFound(ResourceNotFound):
|
||||||
|
|
||||||
class NetworkNotFound(ResourceNotFound):
|
class NetworkNotFound(ResourceNotFound):
|
||||||
message = _("Unable to find network %(network)s.")
|
message = _("Unable to find network %(network)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class TrustCreateFailed(MagnumException):
|
||||||
|
message = _("Failed to create trust for trustee %(trustee_user_id)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class TrustDeleteFailed(MagnumException):
|
||||||
|
message = _("Failed to delete trust %(trust_id)s.")
|
||||||
|
|
|
@ -83,7 +83,8 @@ class KeystoneClientV3(object):
|
||||||
return 'token' in auth_token_info
|
return 'token' in auth_token_info
|
||||||
|
|
||||||
def _get_ks_client(self):
|
def _get_ks_client(self):
|
||||||
kwargs = {'auth_url': self.auth_url}
|
kwargs = {'auth_url': self.auth_url,
|
||||||
|
'endpoint': self.auth_url}
|
||||||
if self.context.trust_id:
|
if self.context.trust_id:
|
||||||
kwargs.update(self._get_admin_credentials())
|
kwargs.update(self._get_admin_credentials())
|
||||||
kwargs['trust_id'] = self.context.trust_id
|
kwargs['trust_id'] = self.context.trust_id
|
||||||
|
@ -112,11 +113,17 @@ class KeystoneClientV3(object):
|
||||||
def create_trust(self, trustee_user, role_names, impersonation=True):
|
def create_trust(self, trustee_user, role_names, impersonation=True):
|
||||||
trustor_user_id = self.client.auth_ref.user_id
|
trustor_user_id = self.client.auth_ref.user_id
|
||||||
trustor_project_id = self.client.auth_ref.project_id
|
trustor_project_id = self.client.auth_ref.project_id
|
||||||
trust = self.client.trusts.create(trustor_user=trustor_user_id,
|
try:
|
||||||
project=trustor_project_id,
|
trust = self.client.trusts.create(
|
||||||
trustee_user=trustee_user,
|
trustor_user=trustor_user_id,
|
||||||
impersonation=impersonation,
|
project=trustor_project_id,
|
||||||
role_names=role_names)
|
trustee_user=trustee_user,
|
||||||
|
impersonation=impersonation,
|
||||||
|
role_names=role_names)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(str(e))
|
||||||
|
raise exception.TrustCreateFailed(
|
||||||
|
trustee_user_id=trustee_user)
|
||||||
return trust
|
return trust
|
||||||
|
|
||||||
def create_trust_to_admin(self, role_names, impersonation=True):
|
def create_trust_to_admin(self, role_names, impersonation=True):
|
||||||
|
@ -124,7 +131,12 @@ class KeystoneClientV3(object):
|
||||||
return self.create_trust(trustee_user, role_names, impersonation)
|
return self.create_trust(trustee_user, role_names, impersonation)
|
||||||
|
|
||||||
def delete_trust(self, trust_id):
|
def delete_trust(self, trust_id):
|
||||||
|
if trust_id is None:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
self.client.trusts.delete(trust_id)
|
self.client.trusts.delete(trust_id)
|
||||||
except kc_exception.NotFound:
|
except kc_exception.NotFound:
|
||||||
pass
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(str(e))
|
||||||
|
raise exception.TrustDeleteFailed(trust_id=trust_id)
|
||||||
|
|
|
@ -51,8 +51,19 @@ bay_heat_opts = [
|
||||||
'interval is in minutes. The default is no timeout.'))
|
'interval is in minutes. The default is no timeout.'))
|
||||||
]
|
]
|
||||||
|
|
||||||
cfg.CONF.register_opts(bay_heat_opts, group='bay_heat')
|
docker_registry_opts = [
|
||||||
|
cfg.StrOpt('trustee_user_id',
|
||||||
|
default=None,
|
||||||
|
help='User id of the trustee'),
|
||||||
|
cfg.ListOpt('trust_roles',
|
||||||
|
default=['registry_user'],
|
||||||
|
help='The roles which are delegated to the trustee '
|
||||||
|
'by the trustor.')
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(bay_heat_opts, group='bay_heat')
|
||||||
|
CONF.register_opts(docker_registry_opts, 'docker_registry')
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -123,6 +134,14 @@ class Handler(object):
|
||||||
|
|
||||||
osc = clients.OpenStackClients(context)
|
osc = clients.OpenStackClients(context)
|
||||||
|
|
||||||
|
baymodel = objects.BayModel.get_by_uuid(context,
|
||||||
|
bay.baymodel_id)
|
||||||
|
if baymodel.registry_enabled:
|
||||||
|
trust = osc.keystone().create_trust(
|
||||||
|
CONF.docker_registry.trustee_user_id,
|
||||||
|
CONF.docker_registry.trust_roles)
|
||||||
|
bay.registry_trust_id = trust.id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Generate certificate and set the cert reference to bay
|
# Generate certificate and set the cert reference to bay
|
||||||
bay.uuid = uuid.uuid4()
|
bay.uuid = uuid.uuid4()
|
||||||
|
@ -199,6 +218,7 @@ class Handler(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
osc.keystone().delete_trust(bay.registry_trust_id)
|
||||||
self._poll_and_check(osc, bay)
|
self._poll_and_check(osc, bay)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""add registry_trust_id to bay
|
||||||
|
|
||||||
|
Revision ID: adc3b7679ae
|
||||||
|
Revises: 40f325033343
|
||||||
|
Create Date: 2015-12-07 15:49:07.622122
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'adc3b7679ae'
|
||||||
|
down_revision = '40f325033343'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('bay', sa.Column('registry_trust_id',
|
||||||
|
sa.String(length=255), nullable=True))
|
|
@ -120,6 +120,8 @@ class Bay(Base):
|
||||||
bay_create_timeout = Column(Integer())
|
bay_create_timeout = Column(Integer())
|
||||||
discovery_url = Column(String(255))
|
discovery_url = Column(String(255))
|
||||||
master_addresses = Column(JSONEncodedList)
|
master_addresses = Column(JSONEncodedList)
|
||||||
|
# TODO(wanghua): encrypt registry_trust_id in db
|
||||||
|
registry_trust_id = Column(String(255))
|
||||||
# (yuanying) if we use barbican,
|
# (yuanying) if we use barbican,
|
||||||
# cert_ref size is determined by below format
|
# cert_ref size is determined by below format
|
||||||
# * http(s)://${DOMAIN_NAME}/v1/containers/${UUID}
|
# * http(s)://${DOMAIN_NAME}/v1/containers/${UUID}
|
||||||
|
|
|
@ -27,7 +27,8 @@ class Bay(base.MagnumPersistentObject, base.MagnumObject,
|
||||||
base.MagnumObjectDictCompat):
|
base.MagnumObjectDictCompat):
|
||||||
# Version 1.0: Initial version
|
# Version 1.0: Initial version
|
||||||
# Version 1.1: Added 'bay_create_timeout' field
|
# Version 1.1: Added 'bay_create_timeout' field
|
||||||
VERSION = '1.1'
|
# Version 1.2: Add 'registry_trust_id' field
|
||||||
|
VERSION = '1.2'
|
||||||
|
|
||||||
dbapi = dbapi.get_instance()
|
dbapi = dbapi.get_instance()
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ class Bay(base.MagnumPersistentObject, base.MagnumObject,
|
||||||
'master_addresses': fields.ListOfStringsField(nullable=True),
|
'master_addresses': fields.ListOfStringsField(nullable=True),
|
||||||
'ca_cert_ref': fields.StringField(nullable=True),
|
'ca_cert_ref': fields.StringField(nullable=True),
|
||||||
'magnum_cert_ref': fields.StringField(nullable=True),
|
'magnum_cert_ref': fields.StringField(nullable=True),
|
||||||
|
'registry_trust_id': fields.StringField(nullable=True)
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -46,6 +46,8 @@ def list_opts():
|
||||||
('conductor', magnum.conductor.config.SERVICE_OPTS),
|
('conductor', magnum.conductor.config.SERVICE_OPTS),
|
||||||
('database', magnum.db.sql_opts),
|
('database', magnum.db.sql_opts),
|
||||||
('docker', magnum.common.docker_utils.docker_opts),
|
('docker', magnum.common.docker_utils.docker_opts),
|
||||||
|
('docker_registry',
|
||||||
|
magnum.conductor.handlers.bay_conductor.docker_registry_opts),
|
||||||
('magnum_client', magnum.common.clients.magnum_client_opts),
|
('magnum_client', magnum.common.clients.magnum_client_opts),
|
||||||
('heat_client', magnum.common.clients.heat_client_opts),
|
('heat_client', magnum.common.clients.heat_client_opts),
|
||||||
('glance_client', magnum.common.clients.glance_client_opts),
|
('glance_client', magnum.common.clients.glance_client_opts),
|
||||||
|
|
|
@ -49,7 +49,8 @@ class KeystoneClientTest(base.BaseTestCase):
|
||||||
ks_client.client
|
ks_client.client
|
||||||
self.assertIsNotNone(ks_client._client)
|
self.assertIsNotNone(ks_client._client)
|
||||||
mock_ks.assert_called_once_with(token='abcd1234',
|
mock_ks.assert_called_once_with(token='abcd1234',
|
||||||
auth_url='http://server.test:5000/v3')
|
auth_url='http://server.test:5000/v3',
|
||||||
|
endpoint='http://server.test:5000/v3')
|
||||||
|
|
||||||
def test_client_with_no_credentials(self, mock_ks):
|
def test_client_with_no_credentials(self, mock_ks):
|
||||||
self.ctx.auth_token = None
|
self.ctx.auth_token = None
|
||||||
|
@ -65,6 +66,7 @@ class KeystoneClientTest(base.BaseTestCase):
|
||||||
self.assertIsNotNone(ks_client._client)
|
self.assertIsNotNone(ks_client._client)
|
||||||
mock_ks.assert_called_once_with(auth_ref={'version': 'v2.0'},
|
mock_ks.assert_called_once_with(auth_ref={'version': 'v2.0'},
|
||||||
auth_url='http://server.test:5000/v3',
|
auth_url='http://server.test:5000/v3',
|
||||||
|
endpoint='http://server.test:5000/v3',
|
||||||
token='abcd1234')
|
token='abcd1234')
|
||||||
|
|
||||||
def test_client_with_v3_auth_token_info(self, mock_ks):
|
def test_client_with_v3_auth_token_info(self, mock_ks):
|
||||||
|
@ -75,6 +77,7 @@ class KeystoneClientTest(base.BaseTestCase):
|
||||||
self.assertIsNotNone(ks_client._client)
|
self.assertIsNotNone(ks_client._client)
|
||||||
mock_ks.assert_called_once_with(auth_ref={'version': 'v3'},
|
mock_ks.assert_called_once_with(auth_ref={'version': 'v3'},
|
||||||
auth_url='http://server.test:5000/v3',
|
auth_url='http://server.test:5000/v3',
|
||||||
|
endpoint='http://server.test:5000/v3',
|
||||||
token='abcd1234')
|
token='abcd1234')
|
||||||
|
|
||||||
def test_client_with_invalid_auth_token_info(self, mock_ks):
|
def test_client_with_invalid_auth_token_info(self, mock_ks):
|
||||||
|
|
|
@ -95,6 +95,8 @@ def get_test_bay(**kw):
|
||||||
'node_count': kw.get('node_count', 3),
|
'node_count': kw.get('node_count', 3),
|
||||||
'master_count': kw.get('master_count', 3),
|
'master_count': kw.get('master_count', 3),
|
||||||
'master_addresses': kw.get('master_addresses', ['172.17.2.18']),
|
'master_addresses': kw.get('master_addresses', ['172.17.2.18']),
|
||||||
|
'registry_trust_id': kw.get('registry_trust_id',
|
||||||
|
'1f2281ac-e532-4e53-bbe6-3c9be24b0504'),
|
||||||
'created_at': kw.get('created_at'),
|
'created_at': kw.get('created_at'),
|
||||||
'updated_at': kw.get('updated_at'),
|
'updated_at': kw.get('updated_at'),
|
||||||
}
|
}
|
||||||
|
|
|
@ -423,7 +423,7 @@ class _TestObject(object):
|
||||||
# For more information on object version testing, read
|
# For more information on object version testing, read
|
||||||
# http://docs.openstack.org/developer/magnum/objects.html
|
# http://docs.openstack.org/developer/magnum/objects.html
|
||||||
object_data = {
|
object_data = {
|
||||||
'Bay': '1.1-aa9937c7453c0fdb9165bd2a83640ed9',
|
'Bay': '1.2-0749bac339a2cc24dc03f45a4359013d',
|
||||||
'BayLock': '1.0-7d1eb08cf2070523bd210369c7a2e076',
|
'BayLock': '1.0-7d1eb08cf2070523bd210369c7a2e076',
|
||||||
'BayModel': '1.8-a4bb0976be245f06edbd1db087a18071',
|
'BayModel': '1.8-a4bb0976be245f06edbd1db087a18071',
|
||||||
'Certificate': '1.0-2aff667971b85c1edf8d15684fd7d5e2',
|
'Certificate': '1.0-2aff667971b85c1edf8d15684fd7d5e2',
|
||||||
|
|
Loading…
Reference in New Issue