Only update necessary metadata

When issuing a metadata update, it is better to simply update the
key/value pairs that we need to update rather than completely deleting
and recreating the dictionary from scratch. It avoids problems such as
when parts of the server metadata have been tagged as immutable by
admins. Metadata keys that cannot be updated due to permissions are
simply skipped over now.

Change-Id: I6f928ce992d48e062ffd2017a665dc16641ac4b3
This commit is contained in:
Jacob Estelle 2019-05-03 22:27:03 -07:00 committed by Jacob Estelle
parent 4764595ac7
commit 985b77a031
3 changed files with 49 additions and 12 deletions

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack import exceptions as sdk_exc
from oslo_config import cfg
from oslo_log import log
@ -195,17 +196,26 @@ class NovaClient(base.DriverBase):
res = self.conn.compute.get_server_metadata(server)
return res.metadata
def _ignore_forbidden_call(self, func, *args, **kwargs):
try:
return func(*args, **kwargs)
except sdk_exc.HttpException as exc:
if exc.status_code != 403:
raise
@sdk.translate_exception
def server_metadata_update(self, server, metadata):
# Clean all existing metadata first
res = self.conn.compute.get_server_metadata(server)
if res.metadata:
self.conn.compute.delete_server_metadata(
server, list(res.metadata.keys()))
# Then reset metadata to given value if it is not {}
for key in res.metadata:
self._ignore_forbidden_call(
self.conn.compute.delete_server_metadata, server, [key])
if metadata:
return self.conn.compute.set_server_metadata(server, **metadata)
for key, value in metadata.items():
self._ignore_forbidden_call(
self.conn.compute.set_server_metadata,
server, **{key: value})
@sdk.translate_exception
def server_metadata_delete(self, server, keys):

View File

@ -1496,7 +1496,7 @@ class ServerProfile(base.Profile):
driver = self.compute(obj)
try:
metadata = driver.server_metadata_get(obj.physical_id) or {}
metadata = {}
metadata['cluster_id'] = cluster_id
metadata['cluster_node_index'] = six.text_type(obj.index)
driver.server_metadata_update(obj.physical_id, metadata)

View File

@ -11,6 +11,7 @@
# under the License.
import mock
from openstack import exceptions as sdk_exc
from oslo_config import cfg
from senlin.drivers.os import nova_v2
@ -435,16 +436,42 @@ class TestNovaV2(base.SenlinTestCase):
server = mock.Mock()
res_server = mock.Mock()
res_server.metadata = {
'k1': 'v1'
'k1': 'v1',
'k2': 'v2'
}
self.compute.get_server_metadata.return_value = res_server
d = nova_v2.NovaClient(self.conn_params)
d.server_metadata_update(server, {'k2': 'v2'})
self.compute.delete_server_metadata.assert_called_once_with(
server, ['k1'])
self.compute.set_server_metadata.assert_called_once_with(
server, k2='v2')
d.server_metadata_update(server, {'k3': 'v3', 'k4': 'v4'})
self.compute.set_server_metadata.assert_has_calls(
[mock.call(server, k3='v3'), mock.call(server, k4='v4')],
any_order=True)
self.compute.delete_server_metadata.assert_has_calls(
[mock.call(server, ['k1']), mock.call(server, ['k2'])],
any_order=True)
def test_server_metadata_update_forbidden(self):
server = mock.Mock()
res_server = mock.Mock()
res_server.metadata = {
'k1': 'v1',
'forbidden_key': 'forbidden_data',
'k2': 'v2'
}
self.compute.get_server_metadata.return_value = res_server
self.compute.delete_server_metadata.side_effect = [
None, sdk_exc.HttpException(http_status=403), None]
self.compute.set_server_metadata.side_effect = [
None, sdk_exc.HttpException(http_status=403), None]
d = nova_v2.NovaClient(self.conn_params)
d.server_metadata_update(server, {'k3': 'v3', 'k4': 'v4', 'k5': 'v5'})
self.compute.set_server_metadata.assert_has_calls(
[mock.call(server, k3='v3'), mock.call(server, k4='v4'),
mock.call(server, k5='v5')], any_order=True)
self.compute.delete_server_metadata.assert_has_calls(
[mock.call(server, ['k1']), mock.call(server, ['forbidden_key']),
mock.call(server, ['k2'])], any_order=True)
def test_server_metadata_delete(self):
server = mock.Mock()