Create GCE Service outside Neutron service start path
Creating gce client inside Neutron service start causes neutron-server crash if service_key is not there at mentioned path. Neutron service should not crash at start if service key is not there. Change-Id: I8e0f8b150c60d55d71846face217d5213b5df9b7 Closes-Bug: #1707872
This commit is contained in:
parent
6c6d849778
commit
8d58fd2f16
@ -11,6 +11,7 @@ License for the specific language governing permissions and limitations
|
|||||||
under the License.
|
under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -75,6 +76,10 @@ class GceResourceNotFound(e.NotFound):
|
|||||||
message = _("GCE Resource %(name)s %(identifier)s was not found")
|
message = _("GCE Resource %(name)s %(identifier)s was not found")
|
||||||
|
|
||||||
|
|
||||||
|
class GceServiceKeyNotFound(e.NotFound):
|
||||||
|
message = _("GCE service key was not found at %(path)s location")
|
||||||
|
|
||||||
|
|
||||||
def list_instances(compute, project, zone):
|
def list_instances(compute, project, zone):
|
||||||
"""Returns list of GCE instance resources for specified project
|
"""Returns list of GCE instance resources for specified project
|
||||||
|
|
||||||
@ -151,6 +156,9 @@ def get_gce_service(service_key):
|
|||||||
:return: :class:`Resource <Resource>` object
|
:return: :class:`Resource <Resource>` object
|
||||||
:rtype: googleapiclient.discovery.Resource
|
:rtype: googleapiclient.discovery.Resource
|
||||||
"""
|
"""
|
||||||
|
if not os.path.exists(service_key):
|
||||||
|
raise GceServiceKeyNotFound(path=service_key)
|
||||||
|
|
||||||
credentials = GoogleCredentials.from_stream(service_key)
|
credentials = GoogleCredentials.from_stream(service_key)
|
||||||
service = build('compute', 'v1', credentials=credentials)
|
service = build('compute', 'v1', credentials=credentials)
|
||||||
return service
|
return service
|
||||||
|
@ -48,13 +48,19 @@ class GceMechanismDriver(api.MechanismDriver):
|
|||||||
self.gce_region = gceconf.region
|
self.gce_region = gceconf.region
|
||||||
self.gce_project = gceconf.project_id
|
self.gce_project = gceconf.project_id
|
||||||
self.gce_svc_key = gceconf.service_key_path
|
self.gce_svc_key = gceconf.service_key_path
|
||||||
|
self._gce_svc = None
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.gce_svc = gceutils.get_gce_service(self.gce_svc_key)
|
|
||||||
LOG.info("GCE Mechanism driver init with %s project, %s region" %
|
LOG.info("GCE Mechanism driver init with %s project, %s region" %
|
||||||
(self.gce_project, self.gce_region))
|
(self.gce_project, self.gce_region))
|
||||||
self._subscribe_events()
|
self._subscribe_events()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gce_svc(self):
|
||||||
|
if self._gce_svc is None:
|
||||||
|
self._gce_svc = gceutils.get_gce_service(self.gce_svc_key)
|
||||||
|
return self._gce_svc
|
||||||
|
|
||||||
def _subscribe_events(self):
|
def _subscribe_events(self):
|
||||||
registry.subscribe(self.secgroup_callback, resources.SECURITY_GROUP,
|
registry.subscribe(self.secgroup_callback, resources.SECURITY_GROUP,
|
||||||
events.BEFORE_DELETE)
|
events.BEFORE_DELETE)
|
||||||
|
@ -75,10 +75,16 @@ class GceRouterPlugin(
|
|||||||
self.gce_region = gceconf.region
|
self.gce_region = gceconf.region
|
||||||
self.gce_project = gceconf.project_id
|
self.gce_project = gceconf.project_id
|
||||||
self.gce_svc_key = gceconf.service_key_path
|
self.gce_svc_key = gceconf.service_key_path
|
||||||
self.gce_svc = gceutils.get_gce_service(self.gce_svc_key)
|
self._gce_svc = None
|
||||||
LOG.info("GCE Router plugin init with %s project, %s region" %
|
LOG.info("GCE Router plugin init with %s project, %s region" %
|
||||||
(self.gce_project, self.gce_region))
|
(self.gce_project, self.gce_region))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gce_svc(self):
|
||||||
|
if self._gce_svc is None:
|
||||||
|
self._gce_svc = gceutils.get_gce_service(self.gce_svc_key)
|
||||||
|
return self._gce_svc
|
||||||
|
|
||||||
def get_plugin_type(self):
|
def get_plugin_type(self):
|
||||||
return plugin_type
|
return plugin_type
|
||||||
|
|
||||||
|
@ -1,178 +1,181 @@
|
|||||||
"""
|
"""
|
||||||
Copyright (c) 2017 Platform9 Systems Inc.
|
Copyright (c) 2017 Platform9 Systems Inc.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
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
|
not use this file except in compliance with the License. You may obtain
|
||||||
a copy of the License at
|
a copy of the License at
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied. See the
|
WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied. See the
|
||||||
License for the specific language governing permissions and limitations
|
License for the specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from neutron.extensions import securitygroup as sg
|
from neutron.extensions import securitygroup as sg
|
||||||
from neutron.manager import NeutronManager
|
from neutron.manager import NeutronManager
|
||||||
from neutron.plugins.ml2.drivers.gce.mech_gce import GceMechanismDriver
|
from neutron.plugins.ml2.drivers.gce.mech_gce import GceMechanismDriver
|
||||||
from neutron.plugins.ml2.drivers.gce.mech_gce import SecurityGroupInvalidDirection # noqa
|
from neutron.plugins.ml2.drivers.gce.mech_gce import SecurityGroupInvalidDirection # noqa
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
from neutron.tests.common.gce import gce_mock
|
from neutron.tests.common.gce import gce_mock
|
||||||
from neutron.tests.common.gce.gce_mock import FakeNeutronManager
|
from neutron.tests.common.gce.gce_mock import FakeNeutronManager
|
||||||
from neutron.tests.unit.extensions import test_securitygroup as test_sg
|
from neutron.tests.unit.extensions import test_securitygroup as test_sg
|
||||||
from neutron_lib import constants as const
|
from neutron_lib import constants as const
|
||||||
|
|
||||||
DATA_DIR = os.path.dirname(os.path.abspath("gce_mock.py")) + '/data'
|
DATA_DIR = os.path.dirname(os.path.abspath("gce_mock.py")) + '/data'
|
||||||
NETWORKS_LINK = "projects/omni-163105/global/networks"
|
NETWORKS_LINK = "projects/omni-163105/global/networks"
|
||||||
NETWORK_LINK = NETWORKS_LINK + "/net-03c4f178-670e-4805-a511-9470ca4a0b06"
|
NETWORK_LINK = NETWORKS_LINK + "/net-03c4f178-670e-4805-a511-9470ca4a0b06"
|
||||||
|
|
||||||
if hasattr(NeutronManager, "get_plugin"):
|
if hasattr(NeutronManager, "get_plugin"):
|
||||||
neutron_get_plugin = 'neutron.manager.NeutronManager.get_plugin'
|
neutron_get_plugin = 'neutron.manager.NeutronManager.get_plugin'
|
||||||
else:
|
else:
|
||||||
neutron_get_plugin = 'neutron_lib.plugins.directory.get_plugin'
|
neutron_get_plugin = 'neutron_lib.plugins.directory.get_plugin'
|
||||||
|
|
||||||
|
|
||||||
class GCENeutronTestCase(test_sg.SecurityGroupsTestCase, base.BaseTestCase):
|
class GCENeutronTestCase(test_sg.SecurityGroupsTestCase, base.BaseTestCase):
|
||||||
@mock.patch('neutron.common.gceutils.get_gce_service')
|
def setUp(self):
|
||||||
def setUp(self, mock_service):
|
super(GCENeutronTestCase, self).setUp()
|
||||||
mock_service.side_effect = gce_mock.get_gce_service
|
self.service_patcher = mock.patch(
|
||||||
super(GCENeutronTestCase, self).setUp()
|
'neutron.common.gceutils.get_gce_service').start()
|
||||||
self._driver = GceMechanismDriver()
|
mock_service = self.service_patcher.start()
|
||||||
self._driver.gce_zone = 'us-central1-c'
|
mock_service.side_effect = gce_mock.get_gce_service
|
||||||
self._driver.gce_region = 'us-central1'
|
self.addCleanup(self.service_patcher.stop)
|
||||||
self._driver.gce_project = 'omni-163105'
|
self._driver = GceMechanismDriver()
|
||||||
self._driver.gce_svc_key = "{0}/omni.json".format(DATA_DIR)
|
self._driver.gce_zone = 'us-central1-c'
|
||||||
self.context = self._create_fake_context()
|
self._driver.gce_region = 'us-central1'
|
||||||
self._driver.initialize()
|
self._driver.gce_project = 'omni-163105'
|
||||||
|
self._driver.gce_svc_key = "{0}/omni.json".format(DATA_DIR)
|
||||||
def _create_fake_context(self):
|
self.context = self._create_fake_context()
|
||||||
context = mock.Mock()
|
self._driver.initialize()
|
||||||
context.current = {}
|
|
||||||
context.current['id'] = "fake_id_1234"
|
def _create_fake_context(self):
|
||||||
context.current['cidr'] = "192.168.1.0/24"
|
context = mock.Mock()
|
||||||
context.current['network_id'] = "fake_network_id_1234"
|
context.current = {}
|
||||||
return context
|
context.current['id'] = "fake_id_1234"
|
||||||
|
context.current['cidr'] = "192.168.1.0/24"
|
||||||
def get_fake_sg_rule(self, ethertype=const.IPv4, direction="ingress",
|
context.current['network_id'] = "fake_network_id_1234"
|
||||||
protocol=const.PROTO_NAME_TCP):
|
return context
|
||||||
data = {
|
|
||||||
'id': 'fake_rule_id',
|
def get_fake_sg_rule(self, ethertype=const.IPv4, direction="ingress",
|
||||||
'security_group_id': '4cd70774-cc67-4a87-9b39-7d1db38eb087',
|
protocol=const.PROTO_NAME_TCP):
|
||||||
'direction': direction,
|
data = {
|
||||||
'protocol': protocol,
|
'id': 'fake_rule_id',
|
||||||
'ethertype': ethertype,
|
'security_group_id': '4cd70774-cc67-4a87-9b39-7d1db38eb087',
|
||||||
'tenant_id': 'fake_tenant_id',
|
'direction': direction,
|
||||||
'port_range_min': '22',
|
'protocol': protocol,
|
||||||
'port_range_max': '22',
|
'ethertype': ethertype,
|
||||||
'remote_ip_prefix': None,
|
'tenant_id': 'fake_tenant_id',
|
||||||
'remote_group_id': None
|
'port_range_min': '22',
|
||||||
}
|
'port_range_max': '22',
|
||||||
return data
|
'remote_ip_prefix': None,
|
||||||
|
'remote_group_id': None
|
||||||
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
}
|
||||||
@mock.patch('neutron.common.gceutils.create_network')
|
return data
|
||||||
def test_create_network_postcommit(self, mock_create, mock_wait):
|
|
||||||
mock_create.side_effect = gce_mock.create_anything
|
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
||||||
mock_wait.side_effect = gce_mock.wait_for_operation
|
@mock.patch('neutron.common.gceutils.create_network')
|
||||||
self.assertIsNone(self._driver.create_network_postcommit(self.context))
|
def test_create_network_postcommit(self, mock_create, mock_wait):
|
||||||
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
mock_create.side_effect = gce_mock.create_anything
|
||||||
self._driver.gce_project,
|
mock_wait.side_effect = gce_mock.wait_for_operation
|
||||||
gce_mock.fake_operation())
|
self.assertIsNone(self._driver.create_network_postcommit(self.context))
|
||||||
|
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
||||||
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
self._driver.gce_project,
|
||||||
@mock.patch('neutron.common.gceutils.delete_network')
|
gce_mock.fake_operation())
|
||||||
def test_delete_network_postcommit(self, mock_delete, mock_wait):
|
|
||||||
mock_delete.side_effect = gce_mock.delete_anything
|
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
||||||
mock_wait.side_effect = gce_mock.wait_for_operation
|
@mock.patch('neutron.common.gceutils.delete_network')
|
||||||
self.assertIsNone(self._driver.delete_network_postcommit(self.context))
|
def test_delete_network_postcommit(self, mock_delete, mock_wait):
|
||||||
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
mock_delete.side_effect = gce_mock.delete_anything
|
||||||
self._driver.gce_project,
|
mock_wait.side_effect = gce_mock.wait_for_operation
|
||||||
gce_mock.fake_operation())
|
self.assertIsNone(self._driver.delete_network_postcommit(self.context))
|
||||||
|
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
||||||
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
self._driver.gce_project,
|
||||||
@mock.patch('neutron.common.gceutils.create_subnet')
|
gce_mock.fake_operation())
|
||||||
@mock.patch('neutron.common.gceutils.get_network')
|
|
||||||
def test_create_subnet_postcommit(self, mock_get, mock_create, mock_wait):
|
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
||||||
mock_get.side_effect = gce_mock.get_network
|
@mock.patch('neutron.common.gceutils.create_subnet')
|
||||||
mock_create.side_effect = gce_mock.create_anything
|
@mock.patch('neutron.common.gceutils.get_network')
|
||||||
mock_wait.side_effect = gce_mock.wait_for_operation
|
def test_create_subnet_postcommit(self, mock_get, mock_create, mock_wait):
|
||||||
self.assertIsNone(self._driver.create_subnet_postcommit(self.context))
|
mock_get.side_effect = gce_mock.get_network
|
||||||
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
mock_create.side_effect = gce_mock.create_anything
|
||||||
self._driver.gce_project,
|
mock_wait.side_effect = gce_mock.wait_for_operation
|
||||||
gce_mock.fake_operation())
|
self.assertIsNone(self._driver.create_subnet_postcommit(self.context))
|
||||||
|
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
||||||
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
self._driver.gce_project,
|
||||||
@mock.patch('neutron.common.gceutils.delete_subnet')
|
gce_mock.fake_operation())
|
||||||
def test_delete_subnet_postcommit(self, mock_delete, mock_wait):
|
|
||||||
mock_delete.side_effect = gce_mock.delete_anything
|
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
||||||
mock_wait.side_effect = gce_mock.wait_for_operation
|
@mock.patch('neutron.common.gceutils.delete_subnet')
|
||||||
self.assertIsNone(self._driver.delete_subnet_postcommit(self.context))
|
def test_delete_subnet_postcommit(self, mock_delete, mock_wait):
|
||||||
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
mock_delete.side_effect = gce_mock.delete_anything
|
||||||
self._driver.gce_project,
|
mock_wait.side_effect = gce_mock.wait_for_operation
|
||||||
gce_mock.fake_operation())
|
self.assertIsNone(self._driver.delete_subnet_postcommit(self.context))
|
||||||
|
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
||||||
def test_convert_sg_to_gce_failure_with_wrong_ethertype(self):
|
self._driver.gce_project,
|
||||||
sg_rule = self.get_fake_sg_rule(ethertype=const.IPv6)
|
gce_mock.fake_operation())
|
||||||
self.assertRaises(sg.SecurityGroupRuleInvalidEtherType,
|
|
||||||
self._driver._convert_secgrp_rule_to_gce,
|
def test_convert_sg_to_gce_failure_with_wrong_ethertype(self):
|
||||||
rule=sg_rule, network_link=NETWORK_LINK)
|
sg_rule = self.get_fake_sg_rule(ethertype=const.IPv6)
|
||||||
|
self.assertRaises(sg.SecurityGroupRuleInvalidEtherType,
|
||||||
def test_convert_sg_to_gce_failure_with_wrong_direction(self):
|
self._driver._convert_secgrp_rule_to_gce,
|
||||||
sg_rule = self.get_fake_sg_rule(direction="egress")
|
rule=sg_rule, network_link=NETWORK_LINK)
|
||||||
self.assertRaises(SecurityGroupInvalidDirection,
|
|
||||||
self._driver._convert_secgrp_rule_to_gce,
|
def test_convert_sg_to_gce_failure_with_wrong_direction(self):
|
||||||
rule=sg_rule, network_link=NETWORK_LINK)
|
sg_rule = self.get_fake_sg_rule(direction="egress")
|
||||||
|
self.assertRaises(SecurityGroupInvalidDirection,
|
||||||
def test_convert_sg_to_gce_failure_with_wrong_protocol(self):
|
self._driver._convert_secgrp_rule_to_gce,
|
||||||
sg_rule = self.get_fake_sg_rule(protocol="fake_protocol")
|
rule=sg_rule, network_link=NETWORK_LINK)
|
||||||
self.assertRaises(sg.SecurityGroupRuleInvalidProtocol,
|
|
||||||
self._driver._convert_secgrp_rule_to_gce,
|
def test_convert_sg_to_gce_failure_with_wrong_protocol(self):
|
||||||
rule=sg_rule, network_link=NETWORK_LINK)
|
sg_rule = self.get_fake_sg_rule(protocol="fake_protocol")
|
||||||
|
self.assertRaises(sg.SecurityGroupRuleInvalidProtocol,
|
||||||
def test_convert_sg_to_gce_success(self):
|
self._driver._convert_secgrp_rule_to_gce,
|
||||||
sg_rule = self.get_fake_sg_rule()
|
rule=sg_rule, network_link=NETWORK_LINK)
|
||||||
gce_rule = self._driver._convert_secgrp_rule_to_gce(
|
|
||||||
sg_rule, NETWORK_LINK)
|
def test_convert_sg_to_gce_success(self):
|
||||||
self.assertTrue(isinstance(gce_rule, dict))
|
sg_rule = self.get_fake_sg_rule()
|
||||||
|
gce_rule = self._driver._convert_secgrp_rule_to_gce(
|
||||||
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
sg_rule, NETWORK_LINK)
|
||||||
@mock.patch('neutron.common.gceutils.create_firewall_rule')
|
self.assertTrue(isinstance(gce_rule, dict))
|
||||||
def test_create_sg_rule(self, mock_create, mock_wait):
|
|
||||||
mock_create.side_effect = gce_mock.create_anything
|
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
||||||
mock_wait.side_effect = gce_mock.wait_for_operation
|
@mock.patch('neutron.common.gceutils.create_firewall_rule')
|
||||||
sg_rule = self.get_fake_sg_rule()
|
def test_create_sg_rule(self, mock_create, mock_wait):
|
||||||
self.assertIsNone(
|
mock_create.side_effect = gce_mock.create_anything
|
||||||
self._driver._create_secgrp_rule(self.context, sg_rule,
|
mock_wait.side_effect = gce_mock.wait_for_operation
|
||||||
NETWORK_LINK))
|
sg_rule = self.get_fake_sg_rule()
|
||||||
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
self.assertIsNone(
|
||||||
self._driver.gce_project,
|
self._driver._create_secgrp_rule(self.context, sg_rule,
|
||||||
gce_mock.fake_operation())
|
NETWORK_LINK))
|
||||||
|
mock_wait.assert_called_once_with(self._driver.gce_svc,
|
||||||
@mock.patch(neutron_get_plugin)
|
self._driver.gce_project,
|
||||||
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
gce_mock.fake_operation())
|
||||||
@mock.patch('neutron.common.gceutils.update_firewall_rule')
|
|
||||||
@mock.patch('neutron.common.gceutils.get_firewall_rule')
|
@mock.patch(neutron_get_plugin)
|
||||||
def test_update_sg_rule(self, mock_get, mock_update, mock_wait,
|
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
||||||
mock_plugin):
|
@mock.patch('neutron.common.gceutils.update_firewall_rule')
|
||||||
mock_get.side_effect = gce_mock.get_firewall_rule
|
@mock.patch('neutron.common.gceutils.get_firewall_rule')
|
||||||
mock_update.side_effect = gce_mock.create_anything
|
def test_update_sg_rule(self, mock_get, mock_update, mock_wait,
|
||||||
mock_wait.side_effect = gce_mock.wait_for_operation
|
mock_plugin):
|
||||||
mock_plugin.side_effect = FakeNeutronManager
|
mock_get.side_effect = gce_mock.get_firewall_rule
|
||||||
sg_rule = self.get_fake_sg_rule()
|
mock_update.side_effect = gce_mock.create_anything
|
||||||
self.assertIsNone(
|
mock_wait.side_effect = gce_mock.wait_for_operation
|
||||||
self._driver._update_secgrp_rule(self.context, sg_rule['id']))
|
mock_plugin.side_effect = FakeNeutronManager
|
||||||
self.assertTrue(mock_update.called)
|
sg_rule = self.get_fake_sg_rule()
|
||||||
|
self.assertIsNone(
|
||||||
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
self._driver._update_secgrp_rule(self.context, sg_rule['id']))
|
||||||
@mock.patch('neutron.common.gceutils.delete_firewall_rule')
|
self.assertTrue(mock_update.called)
|
||||||
def test_delete_sg_rule(self, mock_delete, mock_wait):
|
|
||||||
mock_delete.side_effect = gce_mock.delete_anything
|
@mock.patch('neutron.common.gceutils.wait_for_operation')
|
||||||
mock_wait.side_effect = gce_mock.wait_for_operation
|
@mock.patch('neutron.common.gceutils.delete_firewall_rule')
|
||||||
sg_rule = self.get_fake_sg_rule()
|
def test_delete_sg_rule(self, mock_delete, mock_wait):
|
||||||
self.assertIsNone(
|
mock_delete.side_effect = gce_mock.delete_anything
|
||||||
self._driver._delete_secgrp_rule(self.context, sg_rule['id']))
|
mock_wait.side_effect = gce_mock.wait_for_operation
|
||||||
mock_delete.assert_called_once_with(self._driver.gce_svc,
|
sg_rule = self.get_fake_sg_rule()
|
||||||
self._driver.gce_project,
|
self.assertIsNone(
|
||||||
"secgrp-" + sg_rule['id'])
|
self._driver._delete_secgrp_rule(self.context, sg_rule['id']))
|
||||||
|
mock_delete.assert_called_once_with(self._driver.gce_svc,
|
||||||
|
self._driver.gce_project,
|
||||||
|
"secgrp-" + sg_rule['id'])
|
||||||
|
Loading…
Reference in New Issue
Block a user