Add possibilities to add & delete VMware clusters on operational env
* Allow update VMWareAttributes for locked cluster if has pending addition/deletion 'compute-vmware' nodes * Add validation of 'nova_computes' section in vmware attributes Change-Id: I16c1826de81251f5e6ce626b730988127160202f Implements: blueprint add-vmware-clusters
This commit is contained in:
parent
218e8a2ab3
commit
3450d676de
|
@ -330,7 +330,8 @@ class VmwareAttributesHandler(BaseHandler):
|
||||||
if not attributes:
|
if not attributes:
|
||||||
raise self.http(404, "No vmware attributes found")
|
raise self.http(404, "No vmware attributes found")
|
||||||
|
|
||||||
if cluster.is_locked:
|
if cluster.is_locked and \
|
||||||
|
not objects.Cluster.has_compute_vmware_changes(cluster):
|
||||||
raise self.http(403, "Environment attributes can't be changed "
|
raise self.http(403, "Environment attributes can't be changed "
|
||||||
"after or during deployment.")
|
"after or during deployment.")
|
||||||
|
|
||||||
|
|
|
@ -318,7 +318,7 @@ class AttributesValidator(BasicValidator):
|
||||||
for attrs in data.get('editable', {}).values():
|
for attrs in data.get('editable', {}).values():
|
||||||
if not isinstance(attrs, dict):
|
if not isinstance(attrs, dict):
|
||||||
continue
|
continue
|
||||||
for attr_name, attr in attrs.items():
|
for attr_name, attr in six.iteritems(attrs):
|
||||||
cls.validate_attribute(attr_name, attr)
|
cls.validate_attribute(attr_name, attr)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -441,8 +441,245 @@ class VmwareAttributesValidator(BasicValidator):
|
||||||
|
|
||||||
single_schema = cluster_schema.vmware_attributes_schema
|
single_schema = cluster_schema.vmware_attributes_schema
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_target_node_id(nova_compute_data):
|
||||||
|
return nova_compute_data['target_node']['current']['id']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, data, instance=None):
|
def _validate_updated_attributes(cls, attributes, instance):
|
||||||
|
"""Validate that attributes contains changes only for allowed fields.
|
||||||
|
|
||||||
|
:param attributes: new vmware attribute settings for db instance
|
||||||
|
:param instance: nailgun.db.sqlalchemy.models.VmwareAttributes instance
|
||||||
|
"""
|
||||||
|
metadata = instance.editable.get('metadata', {})
|
||||||
|
db_editable_attributes = instance.editable.get('value', {})
|
||||||
|
new_editable_attributes = attributes.get('editable', {}).get('value')
|
||||||
|
for attribute_metadata in metadata:
|
||||||
|
if attribute_metadata.get('type') == 'array':
|
||||||
|
attribute_name = attribute_metadata['name']
|
||||||
|
cls._check_attribute(
|
||||||
|
attribute_metadata,
|
||||||
|
db_editable_attributes.get(attribute_name),
|
||||||
|
new_editable_attributes.get(attribute_name)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cls._check_attribute(
|
||||||
|
attribute_metadata,
|
||||||
|
db_editable_attributes,
|
||||||
|
new_editable_attributes
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _check_attribute(cls, metadata, attributes, new_attributes):
|
||||||
|
"""Check new_attributes is equal with attributes except editable fields
|
||||||
|
|
||||||
|
:param metadata: dict describes structure and properties of attributes
|
||||||
|
:param attributes: attributes which is the basis for comparison
|
||||||
|
:param new_attributes: attributes with modifications to check
|
||||||
|
"""
|
||||||
|
if type(attributes) != type(new_attributes):
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Value type of '{0}' attribute couldn't be changed.".
|
||||||
|
format(metadata.get('label') or metadata.get('name')),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
# if metadata field contains editable_for_deployed = True, attribute
|
||||||
|
# and all its childs may be changed too. No need to check it.
|
||||||
|
if metadata.get('editable_for_deployed'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# no 'fields' in metadata means that attribute has no any childs(leaf)
|
||||||
|
if 'fields' not in metadata:
|
||||||
|
if attributes != new_attributes:
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Value of '{0}' attribute couldn't be changed.".
|
||||||
|
format(metadata.get('label') or metadata.get('name')),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
fields_sort_functions = {
|
||||||
|
'availability_zones': lambda x: x['az_name'],
|
||||||
|
'nova_computes': lambda x: x['vsphere_cluster']
|
||||||
|
}
|
||||||
|
field_name = metadata['name']
|
||||||
|
if isinstance(attributes, (list, tuple)):
|
||||||
|
if len(attributes) != len(new_attributes):
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Value of '{0}' attribute couldn't be changed.".
|
||||||
|
format(metadata.get('label') or metadata.get('name')),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
attributes = sorted(
|
||||||
|
attributes, key=fields_sort_functions.get(field_name))
|
||||||
|
new_attributes = sorted(
|
||||||
|
new_attributes, key=fields_sort_functions.get(field_name))
|
||||||
|
for item, new_item in six.moves.zip(attributes, new_attributes):
|
||||||
|
for field_metadata in metadata['fields']:
|
||||||
|
cls._check_attribute(field_metadata,
|
||||||
|
item.get(field_metadata['name']),
|
||||||
|
new_item.get(field_metadata['name']))
|
||||||
|
elif isinstance(attributes, dict):
|
||||||
|
for field_metadata in metadata['fields']:
|
||||||
|
cls._check_attribute(field_metadata,
|
||||||
|
attributes.get(field_name),
|
||||||
|
new_attributes.get(field_name))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_nova_computes(cls, attributes, instance):
|
||||||
|
"""Validates a 'nova_computes' attributes from vmware_attributes
|
||||||
|
|
||||||
|
Raise InvalidData exception if new attributes is not valid.
|
||||||
|
|
||||||
|
:param instance: nailgun.db.sqlalchemy.models.VmwareAttributes instance
|
||||||
|
:param attributes: new attributes for db instance for validation
|
||||||
|
"""
|
||||||
|
input_nova_computes = objects.VmwareAttributes.get_nova_computes_attrs(
|
||||||
|
attributes.get('editable'))
|
||||||
|
|
||||||
|
cls.check_nova_compute_duplicate_and_empty_values(input_nova_computes)
|
||||||
|
|
||||||
|
db_nova_computes = objects.VmwareAttributes.get_nova_computes_attrs(
|
||||||
|
instance.editable)
|
||||||
|
if instance.cluster.is_locked:
|
||||||
|
cls.check_operational_controllers_settings(input_nova_computes,
|
||||||
|
db_nova_computes)
|
||||||
|
operational_compute_nodes = objects.Cluster.\
|
||||||
|
get_operational_vmware_compute_nodes(instance.cluster)
|
||||||
|
cls.check_operational_node_settings(
|
||||||
|
input_nova_computes, db_nova_computes, operational_compute_nodes)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_nova_compute_duplicate_and_empty_values(cls, attributes):
|
||||||
|
"""Check 'nova_computes' attributes for empty and duplicate values."""
|
||||||
|
nova_compute_attributes_sets = {
|
||||||
|
'vsphere_cluster': set(),
|
||||||
|
'service_name': set(),
|
||||||
|
'target_node': set()
|
||||||
|
}
|
||||||
|
for nova_compute_data in attributes:
|
||||||
|
for attr, values in six.iteritems(nova_compute_attributes_sets):
|
||||||
|
if attr == 'target_node':
|
||||||
|
settings_value = cls._get_target_node_id(nova_compute_data)
|
||||||
|
if settings_value == 'controllers':
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
settings_value = nova_compute_data.get(attr)
|
||||||
|
if not settings_value:
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Empty value for attribute '{0}' is not allowed".
|
||||||
|
format(attr),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
if settings_value in values:
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Duplicate value '{0}' for attribute '{1}' is "
|
||||||
|
"not allowed".format(settings_value, attr),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
values.add(settings_value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_operational_node_settings(cls, input_nova_computes,
|
||||||
|
db_nova_computes, operational_nodes):
|
||||||
|
"""Validates a 'nova_computes' attributes for operational compute nodes
|
||||||
|
|
||||||
|
Raise InvalidData exception if nova_compute settings will be changed or
|
||||||
|
deleted for deployed nodes with role 'compute-vmware' that wasn't
|
||||||
|
marked for deletion
|
||||||
|
|
||||||
|
:param input_nova_computes: new nova_compute attributes
|
||||||
|
:type input_nova_computes: list of dicts
|
||||||
|
:param db_nova_computes: nova_computes attributes stored in db
|
||||||
|
:type db_nova_computes: list of dicts
|
||||||
|
:param operational_nodes: list of operational vmware-compute nodes
|
||||||
|
:type operational_nodes: list of nailgun.db.sqlalchemy.models.Node
|
||||||
|
"""
|
||||||
|
input_computes_by_node_name = dict(
|
||||||
|
(cls._get_target_node_id(nc), nc) for nc in input_nova_computes)
|
||||||
|
db_computes_by_node_name = dict(
|
||||||
|
(cls._get_target_node_id(nc), nc) for nc in db_nova_computes)
|
||||||
|
|
||||||
|
for node in operational_nodes:
|
||||||
|
node_hostname = node.hostname
|
||||||
|
input_nova_compute = input_computes_by_node_name.get(node_hostname)
|
||||||
|
if not input_nova_compute:
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"The following compute-vmware node couldn't be "
|
||||||
|
"deleted from vSphere cluster: {0}".format(node.name),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
db_nova_compute = db_computes_by_node_name.get(node_hostname)
|
||||||
|
for attr, db_value in six.iteritems(db_nova_compute):
|
||||||
|
if attr != 'target_node' and \
|
||||||
|
db_value != input_nova_compute.get(attr):
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Parameter '{0}' of nova compute instance with target "
|
||||||
|
"node '{1}' couldn't be changed".format(
|
||||||
|
attr, node.name),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_operational_controllers_settings(cls, input_nova_computes,
|
||||||
|
db_nova_computes):
|
||||||
|
"""Check deployed nova computes settings with target = controllers.
|
||||||
|
|
||||||
|
Raise InvalidData exception if any deployed nova computes clusters with
|
||||||
|
target 'controllers' were added, removed or modified.
|
||||||
|
|
||||||
|
:param input_nova_computes: new nova_compute settings
|
||||||
|
:type input_nova_computes: list of dicts
|
||||||
|
:param db_nova_computes: nova_computes settings stored in db
|
||||||
|
:type db_nova_computes: list of dicts
|
||||||
|
"""
|
||||||
|
input_computes_by_vsphere_name = dict(
|
||||||
|
(nc['vsphere_cluster'], nc) for nc in input_nova_computes if
|
||||||
|
cls._get_target_node_id(nc) == 'controllers'
|
||||||
|
)
|
||||||
|
db_clusters_names = set()
|
||||||
|
for db_nova_compute in db_nova_computes:
|
||||||
|
target_name = cls._get_target_node_id(db_nova_compute)
|
||||||
|
if target_name == 'controllers':
|
||||||
|
vsphere_name = db_nova_compute['vsphere_cluster']
|
||||||
|
input_nova_compute = \
|
||||||
|
input_computes_by_vsphere_name.get(vsphere_name)
|
||||||
|
if not input_nova_compute:
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Nova compute instance with target 'controllers' and "
|
||||||
|
"vSphere cluster {0} couldn't be deleted from "
|
||||||
|
"operational environment.".format(vsphere_name),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
for attr, db_value in six.iteritems(db_nova_compute):
|
||||||
|
input_value = input_nova_compute.get(attr)
|
||||||
|
if attr == 'target_node':
|
||||||
|
db_value = cls._get_target_node_id(db_nova_compute)
|
||||||
|
input_value = cls._get_target_node_id(
|
||||||
|
input_nova_compute)
|
||||||
|
if db_value != input_value:
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Parameter '{0}' of nova compute instance with "
|
||||||
|
"vSphere cluster name '{1}' couldn't be changed".
|
||||||
|
format(attr, vsphere_name),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
db_clusters_names.add(vsphere_name)
|
||||||
|
|
||||||
|
input_clusters_names = set(input_computes_by_vsphere_name)
|
||||||
|
if input_clusters_names - db_clusters_names:
|
||||||
|
raise errors.InvalidData(
|
||||||
|
"Nova compute instances with target 'controllers' couldn't be "
|
||||||
|
"added to operational environment. Check nova compute "
|
||||||
|
"instances with the following vSphere cluster names: {0}".
|
||||||
|
format(', '.join(
|
||||||
|
sorted(input_clusters_names - db_clusters_names))),
|
||||||
|
log_message=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, data, instance):
|
||||||
d = cls.validate_json(data)
|
d = cls.validate_json(data)
|
||||||
if 'metadata' in d.get('editable'):
|
if 'metadata' in d.get('editable'):
|
||||||
db_metadata = instance.editable.get('metadata')
|
db_metadata = instance.editable.get('metadata')
|
||||||
|
@ -453,6 +690,10 @@ class VmwareAttributesValidator(BasicValidator):
|
||||||
log_message=True
|
log_message=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if instance.cluster.is_locked:
|
||||||
|
cls._validate_updated_attributes(d, instance)
|
||||||
|
cls._validate_nova_computes(d, instance)
|
||||||
|
|
||||||
# TODO(apopovych): write validation processing from
|
# TODO(apopovych): write validation processing from
|
||||||
# openstack.yaml for vmware
|
# openstack.yaml for vmware
|
||||||
return d
|
return d
|
||||||
|
|
|
@ -1529,6 +1529,7 @@
|
||||||
-
|
-
|
||||||
name: "nova_computes"
|
name: "nova_computes"
|
||||||
type: "array"
|
type: "array"
|
||||||
|
editable_for_deployed: true
|
||||||
fields:
|
fields:
|
||||||
-
|
-
|
||||||
name: "vsphere_cluster"
|
name: "vsphere_cluster"
|
||||||
|
|
|
@ -1284,6 +1284,31 @@ class Cluster(NailgunObject):
|
||||||
instance.nodes if nodes is None else nodes
|
instance.nodes if nodes is None else nodes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def has_compute_vmware_changes(cls, instance):
|
||||||
|
"""Checks if any 'compute-vmware' nodes are waiting for deployment.
|
||||||
|
|
||||||
|
:param instance: cluster for checking
|
||||||
|
:type instance: nailgun.db.sqlalchemy.models.Cluster instance
|
||||||
|
"""
|
||||||
|
compute_vmware_nodes_query = db().query(models.Node).filter_by(
|
||||||
|
cluster_id=instance.id
|
||||||
|
).filter(sa.or_(
|
||||||
|
sa.and_(models.Node.roles.any('compute-vmware'),
|
||||||
|
models.Node.pending_deletion),
|
||||||
|
models.Node.pending_roles.any('compute-vmware')
|
||||||
|
))
|
||||||
|
return db().query(compute_vmware_nodes_query.exists()).scalar()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_operational_vmware_compute_nodes(cls, instance):
|
||||||
|
return db().query(models.Node).filter_by(
|
||||||
|
cluster_id=instance.id
|
||||||
|
).filter(
|
||||||
|
models.Node.roles.any('compute-vmware'),
|
||||||
|
sa.not_(models.Node.pending_deletion)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
|
||||||
class ClusterCollection(NailgunCollection):
|
class ClusterCollection(NailgunCollection):
|
||||||
"""Cluster collection."""
|
"""Cluster collection."""
|
||||||
|
@ -1294,3 +1319,22 @@ class ClusterCollection(NailgunCollection):
|
||||||
|
|
||||||
class VmwareAttributes(NailgunObject):
|
class VmwareAttributes(NailgunObject):
|
||||||
model = models.VmwareAttributes
|
model = models.VmwareAttributes
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_nova_computes_attrs(attributes):
|
||||||
|
return attributes.get('value', {}).get(
|
||||||
|
'availability_zones', [{}])[0].get('nova_computes', [])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_nova_computes_target_nodes(cls, instance):
|
||||||
|
"""Get data of targets node for all nova computes.
|
||||||
|
|
||||||
|
:param instance: nailgun.db.sqlalchemy.models.Cluster instance
|
||||||
|
:returns: list of dicts that represents nova compute targets
|
||||||
|
"""
|
||||||
|
nova_compute_target_nodes = []
|
||||||
|
for nova_compute in cls.get_nova_computes_attrs(instance.editable):
|
||||||
|
target = nova_compute['target_node']['current']
|
||||||
|
if target['id'] != 'controllers':
|
||||||
|
nova_compute_target_nodes.append(target)
|
||||||
|
return nova_compute_target_nodes
|
||||||
|
|
|
@ -1267,13 +1267,18 @@ class CheckBeforeDeploymentTask(object):
|
||||||
vmware_attributes = task.cluster.vmware_attributes
|
vmware_attributes = task.cluster.vmware_attributes
|
||||||
# Old(< 6.1) clusters haven't vmware support
|
# Old(< 6.1) clusters haven't vmware support
|
||||||
if vmware_attributes:
|
if vmware_attributes:
|
||||||
cinder_nodes = filter(
|
cinder_nodes = [node for node in task.cluster.nodes if
|
||||||
lambda node: 'cinder' in node.all_roles,
|
'cinder' in node.all_roles]
|
||||||
task.cluster.nodes)
|
|
||||||
|
|
||||||
if not cinder_nodes:
|
if not cinder_nodes:
|
||||||
logger.info('There is no any node with "cinder" role provided')
|
logger.info('There is no any node with "cinder" role provided')
|
||||||
|
|
||||||
|
compute_vmware_nodes = [node for node in task.cluster.nodes if
|
||||||
|
'compute-vmware' in node.all_roles]
|
||||||
|
if compute_vmware_nodes:
|
||||||
|
cls._check_vmware_nova_computes(compute_vmware_nodes,
|
||||||
|
vmware_attributes)
|
||||||
|
|
||||||
models = {
|
models = {
|
||||||
'settings': attributes,
|
'settings': attributes,
|
||||||
'default': vmware_attributes.editable,
|
'default': vmware_attributes.editable,
|
||||||
|
@ -1290,6 +1295,63 @@ class CheckBeforeDeploymentTask(object):
|
||||||
if errors_msg:
|
if errors_msg:
|
||||||
raise errors.CheckBeforeDeploymentError('\n'.join(errors_msg))
|
raise errors.CheckBeforeDeploymentError('\n'.join(errors_msg))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _check_vmware_nova_computes(cls, compute_vmware_nodes, attributes):
|
||||||
|
"""Check that nova computes settings is correct for cluster nodes
|
||||||
|
|
||||||
|
:param compute_vmware_nodes: all node with role compute-vmware that
|
||||||
|
belongs to cluster
|
||||||
|
:type compute_vmware_nodes: list of nailgun.db.sqlalchemy.models.Node
|
||||||
|
instances
|
||||||
|
:param attributes: cluster vmware_attributes
|
||||||
|
:type attributes: nailgun.db.sqlalchemy.models.VmwareAttributes
|
||||||
|
:raises: errors.CheckBeforeDeploymentError
|
||||||
|
"""
|
||||||
|
compute_nodes_targets = \
|
||||||
|
objects.VmwareAttributes.get_nova_computes_target_nodes(attributes)
|
||||||
|
compute_nodes_hostnames = set([t['id'] for t in compute_nodes_targets])
|
||||||
|
|
||||||
|
errors_msg = []
|
||||||
|
cluster_nodes_hostname = set()
|
||||||
|
not_deleted_nodes_from_computes = set()
|
||||||
|
not_assigned_nodes_to_computes = set()
|
||||||
|
for node in compute_vmware_nodes:
|
||||||
|
node_hostname = node.hostname
|
||||||
|
if node.pending_deletion:
|
||||||
|
if node_hostname in compute_nodes_hostnames:
|
||||||
|
not_deleted_nodes_from_computes.add(node.name)
|
||||||
|
elif node_hostname not in compute_nodes_hostnames:
|
||||||
|
not_assigned_nodes_to_computes.add(node.name)
|
||||||
|
|
||||||
|
cluster_nodes_hostname.add(node_hostname)
|
||||||
|
|
||||||
|
if not_assigned_nodes_to_computes:
|
||||||
|
errors_msg.append(
|
||||||
|
"The following compute-vmware nodes are not assigned to "
|
||||||
|
"any vCenter cluster: {0}".format(
|
||||||
|
', '.join(sorted(not_assigned_nodes_to_computes))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not_deleted_nodes_from_computes:
|
||||||
|
errors_msg.append(
|
||||||
|
"The following nodes are prepared for deletion and "
|
||||||
|
"couldn't be assigned to any vCenter cluster: {0}".format(
|
||||||
|
', '.join(sorted(not_deleted_nodes_from_computes))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
alien_nodes_names = [t['label'] for t in compute_nodes_targets if
|
||||||
|
t['id'] not in cluster_nodes_hostname]
|
||||||
|
if alien_nodes_names:
|
||||||
|
errors_msg.append(
|
||||||
|
"The following nodes don't belong to compute-vmware nodes of "
|
||||||
|
"environment and couldn't be assigned to any vSphere cluster: "
|
||||||
|
"{0}".format(', '.join(sorted(alien_nodes_names)))
|
||||||
|
)
|
||||||
|
|
||||||
|
if errors_msg:
|
||||||
|
raise errors.CheckBeforeDeploymentError('\n'.join(errors_msg))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate_network_template(cls, task):
|
def _validate_network_template(cls, task):
|
||||||
cluster = task.cluster
|
cluster = task.cluster
|
||||||
|
|
|
@ -243,8 +243,8 @@ class EnvironmentManager(object):
|
||||||
cluster_data = {
|
cluster_data = {
|
||||||
'name': 'cluster-api-' + str(randint(0, 1000000)),
|
'name': 'cluster-api-' + str(randint(0, 1000000)),
|
||||||
}
|
}
|
||||||
editable_attributes = kwargs.pop(
|
editable_attributes = kwargs.pop('editable_attributes', None)
|
||||||
'editable_attributes', None)
|
vmware_attributes = kwargs.pop('vmware_attributes', None)
|
||||||
|
|
||||||
if kwargs:
|
if kwargs:
|
||||||
cluster_data.update(kwargs)
|
cluster_data.update(kwargs)
|
||||||
|
@ -277,6 +277,8 @@ class EnvironmentManager(object):
|
||||||
if editable_attributes:
|
if editable_attributes:
|
||||||
Cluster.patch_attributes(cluster_db,
|
Cluster.patch_attributes(cluster_db,
|
||||||
{'editable': editable_attributes})
|
{'editable': editable_attributes})
|
||||||
|
if vmware_attributes:
|
||||||
|
Cluster.update_vmware_attributes(cluster_db, vmware_attributes)
|
||||||
return cluster
|
return cluster
|
||||||
|
|
||||||
def create_node(
|
def create_node(
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from mock import patch
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
@ -653,6 +654,49 @@ class TestVmwareAttributes(BaseIntegrationTest):
|
||||||
resp.json_body["message"]
|
resp.json_body["message"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@patch('nailgun.db.sqlalchemy.models.Cluster.is_locked', return_value=True)
|
||||||
|
def test_vmware_attributes_update_for_locked_cluster_403(self, locked):
|
||||||
|
self._set_use_vcenter(self.cluster_db)
|
||||||
|
resp = self.app.put(
|
||||||
|
reverse(
|
||||||
|
'VmwareAttributesHandler',
|
||||||
|
kwargs={'cluster_id': self.cluster_db.id}),
|
||||||
|
params=jsonutils.dumps({
|
||||||
|
"editable": {
|
||||||
|
"value": {"foo": "bar"}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers=self.default_headers,
|
||||||
|
expect_errors=True
|
||||||
|
)
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
self.assertEqual("Environment attributes can't be changed after or "
|
||||||
|
"during deployment.", resp.json_body["message"])
|
||||||
|
|
||||||
|
@patch('objects.Cluster.has_compute_vmware_changes', return_value=True)
|
||||||
|
@patch('nailgun.db.sqlalchemy.models.Cluster.is_locked', return_value=True)
|
||||||
|
def test_vmware_attributes_update_for_locked_cluster_200(
|
||||||
|
self, is_locked_mock, has_compute_mock):
|
||||||
|
self._set_use_vcenter(self.cluster_db)
|
||||||
|
params = {
|
||||||
|
"editable": {
|
||||||
|
"value": {"foo": "bar"}
|
||||||
|
}}
|
||||||
|
with patch('nailgun.api.v1.handlers.cluster.VmwareAttributesHandler.'
|
||||||
|
'checked_data', return_value=params):
|
||||||
|
resp = self.app.put(
|
||||||
|
reverse(
|
||||||
|
'VmwareAttributesHandler',
|
||||||
|
kwargs={'cluster_id': self.cluster_db.id}),
|
||||||
|
params=jsonutils.dumps(params),
|
||||||
|
headers=self.default_headers
|
||||||
|
)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
attrs = objects.Cluster.get_vmware_attributes(self.cluster_db)
|
||||||
|
self.assertEqual('bar', attrs.editable.get('value', {}).get('foo'))
|
||||||
|
attrs.editable.get('value', {}).pop('foo')
|
||||||
|
self.assertEqual(attrs.editable.get('value'), {})
|
||||||
|
|
||||||
def _set_use_vcenter(self, cluster):
|
def _set_use_vcenter(self, cluster):
|
||||||
cluster_attrs = objects.Cluster.get_editable_attributes(cluster)
|
cluster_attrs = objects.Cluster.get_editable_attributes(cluster)
|
||||||
cluster_attrs['common']['use_vcenter']['value'] = True
|
cluster_attrs['common']['use_vcenter']['value'] = True
|
||||||
|
|
|
@ -1296,6 +1296,27 @@ class TestClusterObject(BaseTestCase):
|
||||||
'cluster': {u'net_provider': u'test_provider'}}
|
'cluster': {u'net_provider': u'test_provider'}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_cluster_has_compute_vmware_changes(self):
|
||||||
|
cluster = self.env.create_cluster(api=False)
|
||||||
|
ready_compute_vmware_node = self.env.create_node(
|
||||||
|
cluster_id=cluster.id,
|
||||||
|
roles=['compute-vmware'],
|
||||||
|
status=consts.NODE_STATUSES.ready
|
||||||
|
)
|
||||||
|
self.env.create_node(cluster_id=cluster.id, pending_addition=True,
|
||||||
|
pending_roles=['controller'])
|
||||||
|
self.assertFalse(objects.Cluster.has_compute_vmware_changes(cluster))
|
||||||
|
|
||||||
|
pending_compute_vmware_node = self.env.create_node(
|
||||||
|
cluster_id=cluster.id,
|
||||||
|
pending_roles=["compute-vmware"]
|
||||||
|
)
|
||||||
|
self.assertTrue(objects.Cluster.has_compute_vmware_changes(cluster))
|
||||||
|
objects.Node.delete(pending_compute_vmware_node)
|
||||||
|
objects.Node.update(
|
||||||
|
ready_compute_vmware_node, {'pending_deletion': True})
|
||||||
|
self.assertTrue(objects.Cluster.has_compute_vmware_changes(cluster))
|
||||||
|
|
||||||
def test_enable_settings_by_components(self):
|
def test_enable_settings_by_components(self):
|
||||||
components = [{
|
components = [{
|
||||||
'name': 'network:neutron:tun',
|
'name': 'network:neutron:tun',
|
||||||
|
|
|
@ -609,6 +609,84 @@ class TestCheckBeforeDeploymentTask(BaseTestCase):
|
||||||
_check_deployment_graph_for_correctness(
|
_check_deployment_graph_for_correctness(
|
||||||
self.task)
|
self.task)
|
||||||
|
|
||||||
|
def test_check_missed_nodes_vmware_nova_computes(self):
|
||||||
|
operational_node = self.env.create_node(
|
||||||
|
roles=['compute-vmware'],
|
||||||
|
cluster_id=self.cluster.id,
|
||||||
|
name='node-1'
|
||||||
|
)
|
||||||
|
pending_addition_node = self.env.create_node(
|
||||||
|
roles=['compute-vmware'],
|
||||||
|
cluster_id=self.cluster.id,
|
||||||
|
pending_addition=True,
|
||||||
|
name='node-2'
|
||||||
|
)
|
||||||
|
msg = ("The following compute-vmware nodes are not assigned to "
|
||||||
|
"any vCenter cluster: {0}").format(', '.join(
|
||||||
|
sorted([operational_node.name, pending_addition_node.name])
|
||||||
|
))
|
||||||
|
with self.assertRaisesRegexp(errors.CheckBeforeDeploymentError, msg):
|
||||||
|
task.CheckBeforeDeploymentTask._check_vmware_consistency(self.task)
|
||||||
|
|
||||||
|
@mock.patch('objects.VmwareAttributes.get_nova_computes_target_nodes')
|
||||||
|
def test_check_not_deleted_nodes_vmware_nova_computes(self, target_nodes):
|
||||||
|
operational_node = self.env.create_node(
|
||||||
|
roles=['compute-vmware'],
|
||||||
|
cluster_id=self.cluster.id,
|
||||||
|
name='node-1'
|
||||||
|
)
|
||||||
|
pending_deletion_node = self.env.create_node(
|
||||||
|
roles=['compute-vmware'],
|
||||||
|
cluster_id=self.cluster.id,
|
||||||
|
pending_deletion=True,
|
||||||
|
name='node-2'
|
||||||
|
)
|
||||||
|
target_nodes.return_value = [{
|
||||||
|
'id': operational_node.hostname,
|
||||||
|
'label': operational_node.name
|
||||||
|
}, {
|
||||||
|
'id': pending_deletion_node.hostname,
|
||||||
|
'label': pending_deletion_node.name
|
||||||
|
}]
|
||||||
|
msg = ("The following nodes are prepared for deletion and couldn't be "
|
||||||
|
"assigned to any vCenter cluster: {0}".format(
|
||||||
|
pending_deletion_node.name))
|
||||||
|
with self.assertRaisesRegexp(errors.CheckBeforeDeploymentError, msg):
|
||||||
|
task.CheckBeforeDeploymentTask._check_vmware_consistency(self.task)
|
||||||
|
|
||||||
|
@mock.patch('objects.VmwareAttributes.get_nova_computes_target_nodes')
|
||||||
|
def test_check_extra_nodes_vmware_nova_computes(self, target_nodes):
|
||||||
|
operational_node = self.env.create_node(
|
||||||
|
roles=['compute-vmware'],
|
||||||
|
cluster_id=self.cluster.id,
|
||||||
|
name='node-1'
|
||||||
|
)
|
||||||
|
non_cluster_node = self.env.create_node(
|
||||||
|
roles=['compute-vmware'],
|
||||||
|
name='node-2'
|
||||||
|
)
|
||||||
|
other_role_node = self.env.create_node(
|
||||||
|
cluster_id=self.cluster.id,
|
||||||
|
name='node-3'
|
||||||
|
)
|
||||||
|
target_nodes.return_value = [{
|
||||||
|
'id': operational_node.hostname,
|
||||||
|
'label': operational_node.name
|
||||||
|
}, {
|
||||||
|
'id': non_cluster_node.hostname,
|
||||||
|
'label': non_cluster_node.name
|
||||||
|
}, {
|
||||||
|
'id': other_role_node.hostname,
|
||||||
|
'label': other_role_node.name
|
||||||
|
}]
|
||||||
|
msg = ("The following nodes don't belong to compute-vmware nodes of "
|
||||||
|
"environment and couldn't be assigned to any vSphere cluster: "
|
||||||
|
"{0}".format(', '.join(
|
||||||
|
sorted([non_cluster_node.name, other_role_node.name]))
|
||||||
|
))
|
||||||
|
with self.assertRaisesRegexp(errors.CheckBeforeDeploymentError, msg):
|
||||||
|
task.CheckBeforeDeploymentTask._check_vmware_consistency(self.task)
|
||||||
|
|
||||||
|
|
||||||
class TestDeployTask(BaseTestCase):
|
class TestDeployTask(BaseTestCase):
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,444 @@
|
||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
from nailgun.api.v1.validators.cluster import VmwareAttributesValidator
|
||||||
|
from nailgun import consts
|
||||||
|
from nailgun.errors import errors
|
||||||
|
from nailgun import objects
|
||||||
|
from nailgun.test.base import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestAttributesValidator(BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestAttributesValidator, self).setUp()
|
||||||
|
self.env.create(
|
||||||
|
cluster_kwargs={
|
||||||
|
"api": False,
|
||||||
|
"vmware_attributes": {
|
||||||
|
"editable": self._get_value_vmware_attributes()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodes_kwargs=[{
|
||||||
|
"hostname": "controller-node",
|
||||||
|
"status": consts.NODE_STATUSES.ready
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
self.cluster = self.env.clusters[0]
|
||||||
|
self.ready_compute_node = self.env.create_node(
|
||||||
|
hostname="node-1",
|
||||||
|
name="Node 1",
|
||||||
|
roles=["compute-vmware"],
|
||||||
|
status=consts.NODE_STATUSES.ready,
|
||||||
|
cluster_id=self.cluster.id
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_target_id(self, nova_compute):
|
||||||
|
return nova_compute["target_node"]["current"]["id"]
|
||||||
|
|
||||||
|
def _get_default_nova_computes(self):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"datastore_regex": ".*",
|
||||||
|
"vsphere_cluster": "Cluster1",
|
||||||
|
"target_node": {
|
||||||
|
"current": {
|
||||||
|
"id": "node-1",
|
||||||
|
"label": "node-1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_name": "ns1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datastore_regex": ".*",
|
||||||
|
"vsphere_cluster": "Cluster0",
|
||||||
|
"target_node": {
|
||||||
|
"current": {
|
||||||
|
"id": "controllers",
|
||||||
|
"label": "controllers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_name": "ns0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_value_vmware_attributes(self, nova_computes=None):
|
||||||
|
return {
|
||||||
|
"value": {
|
||||||
|
"availability_zones": [{
|
||||||
|
"vcenter_username": "admin",
|
||||||
|
"az_name": "vcenter",
|
||||||
|
"vcenter_password": "pass",
|
||||||
|
"vcenter_host": "172.16.0.254",
|
||||||
|
"nova_computes":
|
||||||
|
nova_computes or self._get_default_nova_computes()
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate_nova_compute_raises_regexp(self, nova_computes, error_msg):
|
||||||
|
with self.assertRaisesRegexp(errors.InvalidData, error_msg):
|
||||||
|
VmwareAttributesValidator._validate_nova_computes(
|
||||||
|
{"editable": self._get_value_vmware_attributes(nova_computes)},
|
||||||
|
self.cluster.vmware_attributes
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_change_exist_nova_compute(self):
|
||||||
|
nova_computes = self._get_default_nova_computes()
|
||||||
|
changed_attribute = 'vsphere_cluster'
|
||||||
|
for nc in nova_computes:
|
||||||
|
if self._get_target_id(nc) == self.ready_compute_node.hostname:
|
||||||
|
nc[changed_attribute] = "ClusterXX"
|
||||||
|
break
|
||||||
|
|
||||||
|
self.validate_nova_compute_raises_regexp(
|
||||||
|
nova_computes,
|
||||||
|
"Parameter '{0}' of nova compute instance with target node '{1}' "
|
||||||
|
"couldn't be changed".format(
|
||||||
|
changed_attribute, self.ready_compute_node.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_operational_nova_compute_node(self):
|
||||||
|
nova_computes = [
|
||||||
|
nc for nc in self._get_default_nova_computes() if
|
||||||
|
self._get_target_id(nc) != self.ready_compute_node.hostname
|
||||||
|
]
|
||||||
|
self.validate_nova_compute_raises_regexp(
|
||||||
|
nova_computes,
|
||||||
|
"The following compute-vmware node couldn't be deleted from "
|
||||||
|
"vSphere cluster: {0}".format(self.ready_compute_node.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_duplicate_values_for_nova_computes(self):
|
||||||
|
for attr in ("vsphere_cluster", "target_node", "service_name"):
|
||||||
|
exist_nova_computes = self._get_default_nova_computes()
|
||||||
|
duplicate_value = exist_nova_computes[0][attr]
|
||||||
|
new_nova_compute = {
|
||||||
|
"datastore_regex": ".*",
|
||||||
|
"vsphere_cluster": "ClusterXX",
|
||||||
|
"target_node": {
|
||||||
|
"current": {
|
||||||
|
"id": "node-X",
|
||||||
|
"label": "node-X"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"service_name": "nsXX"
|
||||||
|
}
|
||||||
|
new_nova_compute.update({attr: duplicate_value})
|
||||||
|
exist_nova_computes.append(new_nova_compute)
|
||||||
|
duplicate_value = duplicate_value if attr != "target_node" \
|
||||||
|
else duplicate_value["current"]["id"]
|
||||||
|
|
||||||
|
self.validate_nova_compute_raises_regexp(
|
||||||
|
exist_nova_computes,
|
||||||
|
"Duplicate value '{0}' for attribute '{1}' is not allowed".
|
||||||
|
format(duplicate_value, attr)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_empty_values_for_nova_computes(self):
|
||||||
|
nova_computes = self._get_default_nova_computes()
|
||||||
|
nova_computes[0]["vsphere_cluster"] = ""
|
||||||
|
self.validate_nova_compute_raises_regexp(
|
||||||
|
nova_computes,
|
||||||
|
"Empty value for attribute 'vsphere_cluster' is not allowed"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_nova_compute_setting_validate_pass(self):
|
||||||
|
new_compute_vmware_node = self.env.create_node(
|
||||||
|
hostname="node-2",
|
||||||
|
name="Node 2",
|
||||||
|
pending_roles=["compute-vmware"],
|
||||||
|
pending_addition=True,
|
||||||
|
cluster_id=self.cluster.id
|
||||||
|
)
|
||||||
|
self.env.create_node(
|
||||||
|
hostname="node-3",
|
||||||
|
name="Node 3",
|
||||||
|
roles=["compute-vmware"],
|
||||||
|
pending_deletion=True,
|
||||||
|
cluster_id=self.cluster.id
|
||||||
|
)
|
||||||
|
attributes = self.cluster.vmware_attributes
|
||||||
|
new_nova_computes = self._get_default_nova_computes()
|
||||||
|
new_nova_computes.extend([{
|
||||||
|
"datastore_regex": ".*",
|
||||||
|
"vsphere_cluster": "Cluster2",
|
||||||
|
"target_node": {
|
||||||
|
"current": {
|
||||||
|
"id": new_compute_vmware_node.hostname,
|
||||||
|
"label": new_compute_vmware_node.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_name": "ns2"
|
||||||
|
}])
|
||||||
|
self.assertNotRaises(errors.InvalidData,
|
||||||
|
VmwareAttributesValidator._validate_nova_computes,
|
||||||
|
{"editable": self._get_value_vmware_attributes(
|
||||||
|
new_nova_computes)},
|
||||||
|
attributes)
|
||||||
|
|
||||||
|
def test_change_controller_nova_computes_pass(self):
|
||||||
|
cluster = self.env.create(
|
||||||
|
cluster_kwargs={
|
||||||
|
"api": False,
|
||||||
|
"status": consts.CLUSTER_STATUSES.new,
|
||||||
|
"vmware_attributes": {
|
||||||
|
"editable": self._get_value_vmware_attributes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
new_nova_computes = [nc for nc in self._get_default_nova_computes()
|
||||||
|
if self._get_target_id(nc) == "controllers"]
|
||||||
|
new_nova_computes[0]["vsphere_cluster"] = "new vsphere name"
|
||||||
|
new_nova_computes.append({
|
||||||
|
"datastore_regex": ".*",
|
||||||
|
"vsphere_cluster": "Cluster10",
|
||||||
|
"target_node": {
|
||||||
|
"current": {
|
||||||
|
"id": "controllers",
|
||||||
|
"label": "controllers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_name": "ns10"
|
||||||
|
})
|
||||||
|
self.assertNotRaises(errors.InvalidData,
|
||||||
|
VmwareAttributesValidator._validate_nova_computes,
|
||||||
|
{"editable": self._get_value_vmware_attributes(
|
||||||
|
new_nova_computes)},
|
||||||
|
cluster.vmware_attributes)
|
||||||
|
|
||||||
|
@patch("nailgun.db.sqlalchemy.models.Cluster.is_locked", return_value=True)
|
||||||
|
def test_change_controllers_nova_compute_setting(self, lock_mock):
|
||||||
|
new_nova_computes = self._get_default_nova_computes()
|
||||||
|
changed_vsphere_cluster = None
|
||||||
|
changed_attribute = "service_name"
|
||||||
|
for nc in new_nova_computes:
|
||||||
|
if self._get_target_id(nc) == "controllers":
|
||||||
|
nc[changed_attribute] = "new_service_name"
|
||||||
|
changed_vsphere_cluster = nc
|
||||||
|
break
|
||||||
|
|
||||||
|
self.validate_nova_compute_raises_regexp(
|
||||||
|
new_nova_computes,
|
||||||
|
"Parameter '{0}' of nova compute instance with vSphere cluster "
|
||||||
|
"name '{1}' couldn't be changed".format(
|
||||||
|
changed_attribute, changed_vsphere_cluster["vsphere_cluster"])
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("nailgun.db.sqlalchemy.models.Cluster.is_locked", return_value=True)
|
||||||
|
def test_add_controllers_nova_compute_setting(self, lock_mock):
|
||||||
|
new_nova_computes = self._get_default_nova_computes()
|
||||||
|
new_nova_computes.extend([{
|
||||||
|
"datastore_regex": ".*",
|
||||||
|
"vsphere_cluster": "Cluster20",
|
||||||
|
"target_node": {
|
||||||
|
"current": {
|
||||||
|
"id": "controllers",
|
||||||
|
"label": "controllers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_name": "ns20"
|
||||||
|
}, {
|
||||||
|
"datastore_regex": ".*",
|
||||||
|
"vsphere_cluster": "Cluster30",
|
||||||
|
"target_node": {
|
||||||
|
"current": {
|
||||||
|
"id": "controllers",
|
||||||
|
"label": "controllers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_name": "ns30"
|
||||||
|
}])
|
||||||
|
self.validate_nova_compute_raises_regexp(
|
||||||
|
new_nova_computes,
|
||||||
|
"Nova compute instances with target 'controllers' couldn't be "
|
||||||
|
"added to operational environment. Check nova compute instances "
|
||||||
|
"with the following vSphere cluster names: {0}".format(
|
||||||
|
", ".join(sorted(["Cluster30", "Cluster20"]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("nailgun.db.sqlalchemy.models.Cluster.is_locked", return_value=True)
|
||||||
|
def test_remove_controllers_nova_compute_setting(self, lock_mock):
|
||||||
|
new_nova_computes = [nc for nc in self._get_default_nova_computes()
|
||||||
|
if self._get_target_id(nc) != "controllers"]
|
||||||
|
self.validate_nova_compute_raises_regexp(
|
||||||
|
new_nova_computes,
|
||||||
|
"Nova compute instance with target 'controllers' and vSphere "
|
||||||
|
"cluster {0} couldn't be deleted from operational environment."
|
||||||
|
.format("Cluster0")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_update_non_editable_attributes(self):
|
||||||
|
metadata = [
|
||||||
|
{
|
||||||
|
"name": "foo",
|
||||||
|
"label": "foo",
|
||||||
|
"type": "object",
|
||||||
|
"fields": [{
|
||||||
|
"name": "foo_field_name",
|
||||||
|
"label": "foo_field_name",
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "availability_zones",
|
||||||
|
"label": "availability_zones",
|
||||||
|
"type": "array",
|
||||||
|
"fields": [{
|
||||||
|
"name": "az_name",
|
||||||
|
"label": "az_name",
|
||||||
|
}, {
|
||||||
|
"name": "nova_computes",
|
||||||
|
"type": "array",
|
||||||
|
"fields": [{
|
||||||
|
"name": "vsphere_cluster",
|
||||||
|
"label": "vsphere_cluster",
|
||||||
|
}, {
|
||||||
|
"name": "target_node",
|
||||||
|
"label": "target_node",
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "vcenter_host",
|
||||||
|
"label": "vcenter_host",
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
db_attributes_value = {
|
||||||
|
"availability_zones": [{
|
||||||
|
"az_name": "az_1",
|
||||||
|
"vcenter_host": "127.0.0.1",
|
||||||
|
"nova_computes": [{
|
||||||
|
"vsphere_cluster": "Cluster1",
|
||||||
|
"target_node": {
|
||||||
|
"current": {"id": "node-1"}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"foo": {
|
||||||
|
"foo_field_name": "foo_field_value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance = objects.VmwareAttributes.create(
|
||||||
|
{"editable": {"metadata": metadata, "value": db_attributes_value}}
|
||||||
|
)
|
||||||
|
|
||||||
|
new_attributes = deepcopy(db_attributes_value)
|
||||||
|
new_attributes["foo"] = ["foo_field_name"]
|
||||||
|
msg = "Value type of 'foo_field_name' attribute couldn't be changed."
|
||||||
|
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
||||||
|
VmwareAttributesValidator._validate_updated_attributes(
|
||||||
|
{"editable": {"value": new_attributes}},
|
||||||
|
instance)
|
||||||
|
|
||||||
|
new_attributes = deepcopy(db_attributes_value)
|
||||||
|
new_attributes["foo"]["foo_field_name"] = "new_foo_field_value"
|
||||||
|
msg = "Value of 'foo_field_name' attribute couldn't be changed."
|
||||||
|
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
||||||
|
VmwareAttributesValidator._validate_updated_attributes(
|
||||||
|
{"editable": {"value": new_attributes}},
|
||||||
|
instance)
|
||||||
|
|
||||||
|
new_attributes = deepcopy(db_attributes_value)
|
||||||
|
new_attributes["availability_zones"].append({
|
||||||
|
"az_name": "az_2",
|
||||||
|
"vcenter_host": "127.0.0.1",
|
||||||
|
"nova_computes": []
|
||||||
|
})
|
||||||
|
msg = "Value of 'availability_zones' attribute couldn't be changed."
|
||||||
|
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
||||||
|
VmwareAttributesValidator._validate_updated_attributes(
|
||||||
|
{"editable": {"value": new_attributes}}, instance)
|
||||||
|
|
||||||
|
new_attributes = deepcopy(db_attributes_value)
|
||||||
|
new_attributes["availability_zones"][0]["nova_computes"][0].update(
|
||||||
|
{"target_node": {"current": {"id": "node-2"}}}
|
||||||
|
)
|
||||||
|
msg = "Value of 'target_node' attribute couldn't be changed."
|
||||||
|
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
||||||
|
VmwareAttributesValidator._validate_updated_attributes(
|
||||||
|
{"editable": {"value": new_attributes}}, instance)
|
||||||
|
|
||||||
|
def test_update_editable_attributes(self):
|
||||||
|
metadata = [
|
||||||
|
{
|
||||||
|
"name": "foo",
|
||||||
|
"label": "foo",
|
||||||
|
"type": "object",
|
||||||
|
"editable_for_deployed": True,
|
||||||
|
"fields": [{
|
||||||
|
"name": "foo_field_name",
|
||||||
|
"label": "foo_field_name",
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "availability_zones",
|
||||||
|
"type": "array",
|
||||||
|
"label": "availability_zones",
|
||||||
|
"fields": [{
|
||||||
|
"name": "az_name",
|
||||||
|
"label": "az_name",
|
||||||
|
}, {
|
||||||
|
"name": "nova_computes",
|
||||||
|
"editable_for_deployed": True,
|
||||||
|
"type": "array",
|
||||||
|
"fields": [{
|
||||||
|
"name": "vsphere_cluster",
|
||||||
|
"label": "vsphere_cluster",
|
||||||
|
}, {
|
||||||
|
"name": "target_node",
|
||||||
|
"label": "target_node",
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "vcenter_host",
|
||||||
|
"label": "vcenter_host",
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
db_attributes_value = {
|
||||||
|
"availability_zones": [{
|
||||||
|
"az_name": "az_1",
|
||||||
|
"vcenter_host": "127.0.0.1",
|
||||||
|
"nova_computes": [{
|
||||||
|
"vsphere_cluster": "Cluster1",
|
||||||
|
"target_node": {
|
||||||
|
"current": {"id": "node-1"}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
"foo": {
|
||||||
|
"foo_field_name": "foo_field_value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance = objects.VmwareAttributes.create(
|
||||||
|
{"editable": {"metadata": metadata, "value": db_attributes_value}}
|
||||||
|
)
|
||||||
|
|
||||||
|
new_attributes = deepcopy(db_attributes_value)
|
||||||
|
new_attributes["foo"]["foo_field_name"] = 1
|
||||||
|
new_attributes["availability_zones"][0]["nova_computes"][0].update(
|
||||||
|
{"target_node": {"current": {"id": "node-2"}}}
|
||||||
|
)
|
||||||
|
new_attributes["availability_zones"][0]["nova_computes"].append({
|
||||||
|
"vsphere_cluster": "Cluster2",
|
||||||
|
"target_node": {
|
||||||
|
"current": {"id": "node-2"}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assertNotRaises(
|
||||||
|
errors.InvalidData,
|
||||||
|
VmwareAttributesValidator._validate_updated_attributes,
|
||||||
|
{"editable": {"value": new_attributes}},
|
||||||
|
instance)
|
Loading…
Reference in New Issue