fuel-web/nailgun/nailgun/policy/merge.py
Nikita Zubkov d5a790caab Add exceptions hierarchy
nailgun.errors have a huge set of exceptions but without hierarchy. This
patch remove exception generation from dict and make it explicitly with
python classes and add some exceptions hierarchy. Now all network errors
inherit from NetworkException and same for other exceptions.

Change-Id: I9a2c6b358ea02a16711da74562308664ad7aed97
Closes-bug: #1566195
2016-04-14 17:41:25 +03:00

114 lines
3.5 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# 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.
import abc
import six
from nailgun import errors
@six.add_metaclass(abc.ABCMeta)
class MergePolicy(object):
"""Policy to merge attributes of plugins."""
@abc.abstractmethod
def apply_patch(self, target, patch):
"""Applies patch to the target.
:param target: the origin object, the target can be modified.
:param patch: the modifications for merging with original
:return: the patched object.
"""
class NetworkRoleMergePolicy(MergePolicy):
"""Policy for merging network roles."""
def __init__(self):
self.rules = {'vip': NetworkRoleMergePolicy._patch_vips}
@staticmethod
def _patch_vips(target, patch):
"""Patches VIP attribute for network role.
:param: target: the origin VIPs.
:type target: list
:param patch: the VIPs, that will be added to origin
:type patch: list
:return: the patched VIPs
"""
seen = dict((vip['name'], vip) for vip in target)
# This is a workaround for 8.0 release to allow
# moving controllers to different racks. Will be
# replaced by a different solution in 9.0
if len(patch) == 0:
return []
# END OF WORKAROUND
for vip in patch:
if vip['name'] in seen:
if vip != seen[vip['name']]:
raise ValueError(
"VIP '{0}' conflicts with existing one"
.format(vip['name'])
)
else:
seen[vip['name']] = vip
target.append(vip)
return target
def apply_patch(self, target, patch):
"""Tries to apply patch to the target.
Conflicts will be resolved according to the
predefined rules.
:param target: the origin network role
:type target: dict
:param patch: the modifications for merging with origin
:type patch: dict
:raises: errors.UnresolvableConflict
"""
target_props = target['properties']
patch_props = patch['properties']
conflict = set(target_props) & set(patch_props)
mergeable = set(self.rules)
# Exclude fields that has same value
for name in (conflict - mergeable):
if target_props[name] != patch_props[name]:
raise errors.UnresolvableConflict(
"Cannot apply patch for attribute {0}: conflict"
.format(name)
)
conflict.remove(name)
for name in conflict:
try:
target_props[name] = self.rules[name](
target_props[name],
patch_props[name]
)
except Exception as e:
raise errors.UnresolvableConflict(
"Cannot apply patch for attribute {0}: {1}"
.format(name, e)
)