Extract cluster upgrade to a separate repository
cluster_upgrade extension will be developed by octane team in a separate repository Change-Id: Id0bc78478cf3f40767fed760cd54e487a934fa10
This commit is contained in:
parent
ea6b83a15d
commit
c3294d60ef
|
@ -1,54 +0,0 @@
|
||||||
# A generic, single database configuration.
|
|
||||||
|
|
||||||
[alembic]
|
|
||||||
# path to migration scripts
|
|
||||||
script_location = migrations
|
|
||||||
|
|
||||||
# template used to generate migration files
|
|
||||||
# file_template = %%(rev)s_%%(slug)s
|
|
||||||
|
|
||||||
# max length of characters to apply to the
|
|
||||||
# "slug" field
|
|
||||||
#truncate_slug_length = 40
|
|
||||||
|
|
||||||
# set to 'true' to run the environment during
|
|
||||||
# the 'revision' command, regardless of autogenerate
|
|
||||||
# revision_environment = false
|
|
||||||
|
|
||||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
|
||||||
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
[loggers]
|
|
||||||
keys = root,sqlalchemy,alembic
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
keys = console
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
keys = generic
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
level = WARN
|
|
||||||
handlers = console
|
|
||||||
qualname =
|
|
||||||
|
|
||||||
[logger_sqlalchemy]
|
|
||||||
level = WARN
|
|
||||||
handlers =
|
|
||||||
qualname = sqlalchemy.engine
|
|
||||||
|
|
||||||
[logger_alembic]
|
|
||||||
level = INFO
|
|
||||||
handlers =
|
|
||||||
qualname = alembic
|
|
||||||
|
|
||||||
[handler_console]
|
|
||||||
class = StreamHandler
|
|
||||||
args = (sys.stderr,)
|
|
||||||
level = NOTSET
|
|
||||||
formatter = generic
|
|
||||||
|
|
||||||
[formatter_generic]
|
|
||||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
||||||
datefmt = %H:%M:%S
|
|
|
@ -1,90 +0,0 @@
|
||||||
# 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 __future__ import with_statement
|
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
from logging.config import fileConfig
|
|
||||||
from sqlalchemy import engine_from_config
|
|
||||||
from sqlalchemy import pool
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
|
||||||
# access to the values within the .ini file in use.
|
|
||||||
config = context.config
|
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
|
||||||
# This line sets up loggers basically.
|
|
||||||
fileConfig(config.config_file_name)
|
|
||||||
|
|
||||||
# add your model's MetaData object here
|
|
||||||
# for 'autogenerate' support
|
|
||||||
# from myapp import mymodel
|
|
||||||
# target_metadata = mymodel.Base.metadata
|
|
||||||
target_metadata = None
|
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
|
||||||
# can be acquired:
|
|
||||||
# my_important_option = config.get_main_option("my_important_option")
|
|
||||||
# ... etc.
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline():
|
|
||||||
"""Run migrations in 'offline' mode.
|
|
||||||
|
|
||||||
This configures the context with just a URL
|
|
||||||
and not an Engine, though an Engine is acceptable
|
|
||||||
here as well. By skipping the Engine creation
|
|
||||||
we don't even need a DBAPI to be available.
|
|
||||||
|
|
||||||
Calls to context.execute() here emit the given string to the
|
|
||||||
script output.
|
|
||||||
|
|
||||||
"""
|
|
||||||
context.configure(
|
|
||||||
url=config.get_main_option('sqlalchemy.url'),
|
|
||||||
version_table=config.get_main_option('version_table'))
|
|
||||||
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_online():
|
|
||||||
"""Run migrations in 'online' mode.
|
|
||||||
|
|
||||||
In this scenario we need to create an Engine
|
|
||||||
and associate a connection with the context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
engine = engine_from_config(
|
|
||||||
config.get_section(config.config_ini_section),
|
|
||||||
prefix='sqlalchemy.',
|
|
||||||
poolclass=pool.NullPool)
|
|
||||||
connection = engine.connect()
|
|
||||||
|
|
||||||
context.configure(
|
|
||||||
connection=connection,
|
|
||||||
target_metadata=target_metadata,
|
|
||||||
version_table=config.get_main_option('version_table'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
with context.begin_transaction():
|
|
||||||
context.run_migrations()
|
|
||||||
finally:
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
|
|
||||||
if context.is_offline_mode():
|
|
||||||
run_migrations_offline()
|
|
||||||
else:
|
|
||||||
run_migrations_online()
|
|
|
@ -1,24 +0,0 @@
|
||||||
"""${message}
|
|
||||||
|
|
||||||
Revision ID: ${up_revision}
|
|
||||||
Revises: ${down_revision | comma,n}
|
|
||||||
Create Date: ${create_date}
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = ${repr(up_revision)}
|
|
||||||
down_revision = ${repr(down_revision)}
|
|
||||||
branch_labels = ${repr(branch_labels)}
|
|
||||||
depends_on = ${repr(depends_on)}
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
${imports if imports else ""}
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
${upgrades if upgrades else "pass"}
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
${downgrades if downgrades else "pass"}
|
|
|
@ -1,51 +0,0 @@
|
||||||
# -*- 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.
|
|
||||||
|
|
||||||
"""cluster_upgrade
|
|
||||||
|
|
||||||
Revision ID: 3b5d115d7e49
|
|
||||||
Revises: None
|
|
||||||
Create Date: 2015-07-17 19:46:59.579553
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '3b5d115d7e49'
|
|
||||||
down_revision = None
|
|
||||||
|
|
||||||
|
|
||||||
from alembic import context
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
table_prefix = context.config.get_main_option('table_prefix')
|
|
||||||
table_upgrade_relation_name = '{0}relations'.format(table_prefix)
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
op.create_table(
|
|
||||||
table_upgrade_relation_name,
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('orig_cluster_id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('seed_cluster_id', sa.Integer(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
sa.UniqueConstraint('orig_cluster_id'),
|
|
||||||
sa.UniqueConstraint('seed_cluster_id'))
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
op.drop_table(table_upgrade_relation_name)
|
|
|
@ -1,47 +0,0 @@
|
||||||
# -*- 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 os
|
|
||||||
|
|
||||||
from nailgun import extensions
|
|
||||||
|
|
||||||
from . import handlers
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterUpgradeExtension(extensions.BaseExtension):
|
|
||||||
name = 'cluster_upgrade'
|
|
||||||
version = '0.0.1'
|
|
||||||
description = "Cluster Upgrade Extension"
|
|
||||||
|
|
||||||
urls = [
|
|
||||||
{'uri': r'/clusters/(?P<cluster_id>\d+)/upgrade/clone/?$',
|
|
||||||
'handler': handlers.ClusterUpgradeCloneHandler},
|
|
||||||
{'uri': r'/clusters/(?P<cluster_id>\d+)/upgrade/assign/?$',
|
|
||||||
'handler': handlers.NodeReassignHandler},
|
|
||||||
{'uri': r'/clusters/(?P<cluster_id>\d+)/upgrade/vips/?$',
|
|
||||||
'handler': handlers.CopyVIPsHandler},
|
|
||||||
]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def alembic_migrations_path(cls):
|
|
||||||
return os.path.join(os.path.dirname(__file__),
|
|
||||||
'alembic_migrations', 'migrations')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def on_cluster_delete(cls, cluster):
|
|
||||||
from .objects import relations
|
|
||||||
|
|
||||||
relations.UpgradeRelationObject.delete_relation(cluster.id)
|
|
|
@ -1,141 +0,0 @@
|
||||||
# -*- 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 six
|
|
||||||
|
|
||||||
from nailgun.api.v1.handlers import base
|
|
||||||
from nailgun import objects
|
|
||||||
from nailgun.task import manager
|
|
||||||
|
|
||||||
from . import upgrade
|
|
||||||
from . import validators
|
|
||||||
from .objects import adapters
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterUpgradeCloneHandler(base.BaseHandler):
|
|
||||||
single = objects.Cluster
|
|
||||||
validator = validators.ClusterUpgradeValidator
|
|
||||||
|
|
||||||
@base.content
|
|
||||||
def POST(self, cluster_id):
|
|
||||||
"""Initialize the upgrade of the cluster.
|
|
||||||
|
|
||||||
Creates a new cluster with specified name and release_id. The
|
|
||||||
new cluster is created with parameters that are copied from the
|
|
||||||
cluster with the given cluster_id. The values of the generated
|
|
||||||
and editable attributes are just copied from one to the other.
|
|
||||||
|
|
||||||
:param cluster_id: ID of the cluster from which parameters would
|
|
||||||
be copied
|
|
||||||
:returns: JSON representation of the created cluster
|
|
||||||
:http: * 200 (OK)
|
|
||||||
* 400 (upgrade parameters are invalid)
|
|
||||||
* 404 (node or release not found in db)
|
|
||||||
"""
|
|
||||||
orig_cluster = adapters.NailgunClusterAdapter(
|
|
||||||
self.get_object_or_404(self.single, cluster_id))
|
|
||||||
request_data = self.checked_data(cluster=orig_cluster)
|
|
||||||
new_cluster = upgrade.UpgradeHelper.clone_cluster(orig_cluster,
|
|
||||||
request_data)
|
|
||||||
return new_cluster.to_json()
|
|
||||||
|
|
||||||
|
|
||||||
class NodeReassignHandler(base.BaseHandler):
|
|
||||||
single = objects.Cluster
|
|
||||||
validator = validators.NodeReassignValidator
|
|
||||||
task_manager = manager.ProvisioningTaskManager
|
|
||||||
|
|
||||||
def handle_task(self, cluster_id, nodes):
|
|
||||||
try:
|
|
||||||
task_manager = self.task_manager(cluster_id=cluster_id)
|
|
||||||
task = task_manager.execute(nodes)
|
|
||||||
except Exception as exc:
|
|
||||||
raise self.http(400, msg=six.text_type(exc))
|
|
||||||
|
|
||||||
self.raise_task(task)
|
|
||||||
|
|
||||||
@base.content
|
|
||||||
def POST(self, cluster_id):
|
|
||||||
"""Reassign node to the given cluster.
|
|
||||||
|
|
||||||
The given node will be assigned from the current cluster to the
|
|
||||||
given cluster, by default it involves the reprovisioning of this
|
|
||||||
node. If the 'reprovision' flag is set to False, then the node
|
|
||||||
will be just reassigned. If the 'roles' list is specified, then
|
|
||||||
the given roles will be used as 'pending_roles' in case of
|
|
||||||
the reprovisioning or otherwise as 'roles'.
|
|
||||||
|
|
||||||
:param cluster_id: ID of the cluster node should be assigned to.
|
|
||||||
:returns: None
|
|
||||||
:http: * 202 (OK)
|
|
||||||
* 400 (Incorrect node state, problem with task execution,
|
|
||||||
conflicting or incorrect roles)
|
|
||||||
* 404 (Cluster or node not found)
|
|
||||||
"""
|
|
||||||
cluster = adapters.NailgunClusterAdapter(
|
|
||||||
self.get_object_or_404(self.single, cluster_id))
|
|
||||||
|
|
||||||
data = self.checked_data(cluster=cluster)
|
|
||||||
node = adapters.NailgunNodeAdapter(
|
|
||||||
self.get_object_or_404(objects.Node, data['node_id']))
|
|
||||||
reprovision = data.get('reprovision', True)
|
|
||||||
given_roles = data.get('roles', [])
|
|
||||||
|
|
||||||
roles, pending_roles = upgrade.UpgradeHelper.get_node_roles(
|
|
||||||
reprovision, node.roles, given_roles)
|
|
||||||
upgrade.UpgradeHelper.assign_node_to_cluster(
|
|
||||||
node, cluster, roles, pending_roles)
|
|
||||||
|
|
||||||
if reprovision:
|
|
||||||
self.handle_task(cluster_id, [node.node])
|
|
||||||
|
|
||||||
|
|
||||||
class CopyVIPsHandler(base.BaseHandler):
|
|
||||||
single = objects.Cluster
|
|
||||||
validator = validators.CopyVIPsValidator
|
|
||||||
|
|
||||||
@base.content
|
|
||||||
def POST(self, cluster_id):
|
|
||||||
"""Copy VIPs from original cluster to new one
|
|
||||||
|
|
||||||
Original cluster object is obtained from existing relation between
|
|
||||||
clusters that is created on cluster clone operation
|
|
||||||
|
|
||||||
:param cluster_id: id of cluster that VIPs must be copied to
|
|
||||||
|
|
||||||
:http: * 200 (OK)
|
|
||||||
* 400 (validation failed)
|
|
||||||
* 404 (seed cluster is not found)
|
|
||||||
"""
|
|
||||||
from .objects import relations
|
|
||||||
|
|
||||||
cluster = self.get_object_or_404(self.single, cluster_id)
|
|
||||||
relation = relations.UpgradeRelationObject.get_cluster_relation(
|
|
||||||
cluster.id)
|
|
||||||
|
|
||||||
self.checked_data(cluster=cluster, relation=relation)
|
|
||||||
|
|
||||||
# get original cluster object and create adapter with it
|
|
||||||
orig_cluster_adapter = \
|
|
||||||
adapters.NailgunClusterAdapter(
|
|
||||||
adapters.NailgunClusterAdapter.get_by_uid(
|
|
||||||
relation.orig_cluster_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
seed_cluster_adapter = adapters.NailgunClusterAdapter(cluster)
|
|
||||||
|
|
||||||
upgrade.UpgradeHelper.copy_vips(orig_cluster_adapter,
|
|
||||||
seed_cluster_adapter)
|
|
|
@ -1,31 +0,0 @@
|
||||||
# -*- 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 sqlalchemy import Column
|
|
||||||
from sqlalchemy import Integer
|
|
||||||
|
|
||||||
from nailgun.db.sqlalchemy.models.base import Base
|
|
||||||
|
|
||||||
from . import extension
|
|
||||||
|
|
||||||
|
|
||||||
class UpgradeRelation(Base):
|
|
||||||
__tablename__ = '{0}relations'.format(
|
|
||||||
extension.ClusterUpgradeExtension.table_prefix())
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
orig_cluster_id = Column(Integer, unique=True, nullable=False)
|
|
||||||
seed_cluster_id = Column(Integer, unique=True, nullable=False)
|
|
|
@ -1,211 +0,0 @@
|
||||||
# -*- 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 nailgun import objects
|
|
||||||
|
|
||||||
|
|
||||||
class NailgunClusterAdapter(object):
|
|
||||||
def __init__(self, cluster):
|
|
||||||
self.cluster = cluster
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, data):
|
|
||||||
cluster = objects.Cluster.create(data)
|
|
||||||
return cls(cluster)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self.cluster.id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.cluster.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def net_provider(self):
|
|
||||||
return self.cluster.net_provider
|
|
||||||
|
|
||||||
@property
|
|
||||||
def release(self):
|
|
||||||
return NailgunReleaseAdapter(self.cluster.release)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attributes(self):
|
|
||||||
return self.cluster.attributes
|
|
||||||
|
|
||||||
@property
|
|
||||||
def generated_attrs(self):
|
|
||||||
return self.cluster.attributes.generated
|
|
||||||
|
|
||||||
@generated_attrs.setter
|
|
||||||
def generated_attrs(self, attrs):
|
|
||||||
self.cluster.attributes.generated = attrs
|
|
||||||
|
|
||||||
@property
|
|
||||||
def editable_attrs(self):
|
|
||||||
return self.cluster.attributes.editable
|
|
||||||
|
|
||||||
@property
|
|
||||||
def network_template(self):
|
|
||||||
return self.cluster.network_config.configuration_template
|
|
||||||
|
|
||||||
@editable_attrs.setter
|
|
||||||
def editable_attrs(self, attrs):
|
|
||||||
self.cluster.attributes.editable = attrs
|
|
||||||
|
|
||||||
def get_create_data(self):
|
|
||||||
return objects.Cluster.get_create_data(self.cluster)
|
|
||||||
|
|
||||||
def get_network_manager(self):
|
|
||||||
net_manager = objects.Cluster.get_network_manager(
|
|
||||||
instance=self.cluster)
|
|
||||||
return NailgunNetworkManager(self.cluster, net_manager)
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return objects.Cluster.to_json(self.cluster)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_uid(cls, cluster_id):
|
|
||||||
cluster = objects.Cluster.get_by_uid(cluster_id)
|
|
||||||
return cls(cluster)
|
|
||||||
|
|
||||||
def get_network_groups(self):
|
|
||||||
return (NailgunNetworkGroupAdapter(ng)
|
|
||||||
for ng in self.cluster.network_groups)
|
|
||||||
|
|
||||||
def get_admin_network_group(self):
|
|
||||||
return objects.NetworkGroup.get_admin_network_group()
|
|
||||||
|
|
||||||
|
|
||||||
class NailgunReleaseAdapter(object):
|
|
||||||
def __init__(self, release):
|
|
||||||
self.release = release
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_uid(cls, uid, fail_if_not_found=False):
|
|
||||||
release = objects.Release.get_by_uid(
|
|
||||||
uid, fail_if_not_found=fail_if_not_found)
|
|
||||||
return release
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_deployable(self):
|
|
||||||
return objects.Release.is_deployable(self.release)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def environment_version(self):
|
|
||||||
return self.release.environment_version
|
|
||||||
|
|
||||||
@property
|
|
||||||
def roles_metadata(self):
|
|
||||||
return self.release.roles_metadata
|
|
||||||
|
|
||||||
def __cmp__(self, other):
|
|
||||||
if isinstance(other, NailgunReleaseAdapter):
|
|
||||||
other = other.release
|
|
||||||
return self.release.__cmp__(other)
|
|
||||||
|
|
||||||
|
|
||||||
class NailgunNetworkManager(object):
|
|
||||||
def __init__(self, cluster, net_manager):
|
|
||||||
self.cluster = cluster
|
|
||||||
self.net_manager = net_manager
|
|
||||||
|
|
||||||
def update(self, network_configuration):
|
|
||||||
self.net_manager.update(self.cluster, network_configuration)
|
|
||||||
|
|
||||||
def get_assigned_vips(self):
|
|
||||||
return self.net_manager.get_assigned_vips(self.cluster)
|
|
||||||
|
|
||||||
def assign_vips_for_net_groups(self):
|
|
||||||
return self.net_manager.assign_vips_for_net_groups(self.cluster)
|
|
||||||
|
|
||||||
def assign_given_vips_for_net_groups(self, vips):
|
|
||||||
self.net_manager.assign_given_vips_for_net_groups(self.cluster, vips)
|
|
||||||
|
|
||||||
def set_node_netgroups_ids(self, node, mapping):
|
|
||||||
return objects.Node.set_netgroups_ids(node.node, mapping)
|
|
||||||
|
|
||||||
def set_nic_assignment_netgroups_ids(self, node, mapping):
|
|
||||||
return objects.Node.set_nic_assignment_netgroups_ids(
|
|
||||||
node.node, mapping)
|
|
||||||
|
|
||||||
def set_bond_assignment_netgroups_ids(self, node, mapping):
|
|
||||||
return objects.Node.set_bond_assignment_netgroups_ids(
|
|
||||||
node.node, mapping)
|
|
||||||
|
|
||||||
|
|
||||||
class NailgunNodeAdapter(object):
|
|
||||||
|
|
||||||
def __new__(cls, node=None):
|
|
||||||
if not node:
|
|
||||||
return None
|
|
||||||
return super(NailgunNodeAdapter, cls).__new__(cls, node)
|
|
||||||
|
|
||||||
def __init__(self, node):
|
|
||||||
self.node = node
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self.node.id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cluster_id(self):
|
|
||||||
return self.node.cluster_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hostname(self):
|
|
||||||
return self.node.hostname
|
|
||||||
|
|
||||||
@hostname.setter
|
|
||||||
def hostname(self, hostname):
|
|
||||||
self.node.hostname = hostname
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status(self):
|
|
||||||
return self.node.status
|
|
||||||
|
|
||||||
@property
|
|
||||||
def error_type(self):
|
|
||||||
return self.node.error_type
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_uid(cls, node_id):
|
|
||||||
return cls(objects.Node.get_by_uid(node_id))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def roles(self):
|
|
||||||
return self.node.roles
|
|
||||||
|
|
||||||
def update_cluster_assignment(self, cluster, roles, pending_roles):
|
|
||||||
objects.Node.update_cluster_assignment(self.node, cluster, roles,
|
|
||||||
pending_roles)
|
|
||||||
|
|
||||||
def add_pending_change(self, change):
|
|
||||||
objects.Node.add_pending_change(self.node, change)
|
|
||||||
|
|
||||||
|
|
||||||
class NailgunNetworkGroupAdapter(object):
|
|
||||||
|
|
||||||
def __init__(self, network_group):
|
|
||||||
self.network_group = network_group
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self.network_group.id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.network_group.name
|
|
|
@ -1,48 +0,0 @@
|
||||||
# -*- 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 nailgun.db import db
|
|
||||||
|
|
||||||
from .. import models
|
|
||||||
|
|
||||||
|
|
||||||
class UpgradeRelationObject(object):
|
|
||||||
@staticmethod
|
|
||||||
def _query_cluster_relations(cluster_id):
|
|
||||||
return db.query(models.UpgradeRelation).filter(
|
|
||||||
(models.UpgradeRelation.orig_cluster_id == cluster_id) |
|
|
||||||
(models.UpgradeRelation.seed_cluster_id == cluster_id))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_cluster_relation(cls, cluster_id):
|
|
||||||
return cls._query_cluster_relations(cluster_id).first()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def delete_relation(cls, cluster_id):
|
|
||||||
cls._query_cluster_relations(cluster_id).delete()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_cluster_in_upgrade(cls, cluster_id):
|
|
||||||
query = cls._query_cluster_relations(cluster_id).exists()
|
|
||||||
return db.query(query).scalar()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_relation(cls, orig_cluster_id, seed_cluster_id):
|
|
||||||
relation = models.UpgradeRelation(
|
|
||||||
orig_cluster_id=orig_cluster_id,
|
|
||||||
seed_cluster_id=seed_cluster_id)
|
|
||||||
db.add(relation)
|
|
||||||
db.flush()
|
|
|
@ -1,17 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright 2013 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.
|
|
||||||
|
|
||||||
EXTENSION = "nailgun.extensions.cluster_upgrade."
|
|
|
@ -1,55 +0,0 @@
|
||||||
# -*- 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 nailgun import consts
|
|
||||||
from nailgun.test import base as nailgun_test_base
|
|
||||||
|
|
||||||
from .. import upgrade
|
|
||||||
from ..objects import adapters
|
|
||||||
|
|
||||||
|
|
||||||
class BaseCloneClusterTest(nailgun_test_base.BaseIntegrationTest):
|
|
||||||
helper = upgrade.UpgradeHelper
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(BaseCloneClusterTest, self).setUp()
|
|
||||||
self.src_release = self.env.create_release(
|
|
||||||
operating_system=consts.RELEASE_OS.ubuntu,
|
|
||||||
version="2014.2.2-6.1",
|
|
||||||
state=consts.RELEASE_STATES.manageonly
|
|
||||||
)
|
|
||||||
|
|
||||||
self.dst_release = self.env.create_release(
|
|
||||||
operating_system=consts.RELEASE_OS.ubuntu,
|
|
||||||
version="liberty-9.0",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.src_cluster_db = self.env.create(
|
|
||||||
cluster_kwargs={
|
|
||||||
'api': False,
|
|
||||||
'release_id': self.src_release.id,
|
|
||||||
'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron,
|
|
||||||
'net_l23_provider': consts.NEUTRON_L23_PROVIDERS.ovs,
|
|
||||||
},
|
|
||||||
nodes_kwargs=[{'roles': ['controller']}]
|
|
||||||
)
|
|
||||||
self.src_cluster = adapters.NailgunClusterAdapter(
|
|
||||||
self.src_cluster_db)
|
|
||||||
|
|
||||||
self.data = {
|
|
||||||
"name": "cluster-clone-{0}".format(self.src_cluster.id),
|
|
||||||
"release_id": self.dst_release.id,
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
# -*- 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 alembic
|
|
||||||
|
|
||||||
from nailgun import db
|
|
||||||
from nailgun.db.migration import make_alembic_config_from_extension
|
|
||||||
from nailgun.test import base
|
|
||||||
|
|
||||||
from .. import extension
|
|
||||||
|
|
||||||
|
|
||||||
_test_revision = '3b5d115d7e49'
|
|
||||||
|
|
||||||
|
|
||||||
def setup_module(module):
|
|
||||||
alembic_config = make_alembic_config_from_extension(
|
|
||||||
extension.ClusterUpgradeExtension)
|
|
||||||
db.dropdb()
|
|
||||||
alembic.command.upgrade(alembic_config, _test_revision)
|
|
||||||
|
|
||||||
|
|
||||||
class TestAddRelations(base.BaseAlembicMigrationTest):
|
|
||||||
|
|
||||||
def test_works_without_core_migrations(self):
|
|
||||||
columns = [
|
|
||||||
t.name for t in
|
|
||||||
self.meta.tables['cluster_upgrade_relations'].columns]
|
|
||||||
|
|
||||||
self.assertItemsEqual(columns, [
|
|
||||||
'id',
|
|
||||||
'orig_cluster_id',
|
|
||||||
'seed_cluster_id',
|
|
||||||
])
|
|
|
@ -1,29 +0,0 @@
|
||||||
# -*- 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 mock
|
|
||||||
from nailgun.test.base import BaseTestCase
|
|
||||||
|
|
||||||
from .. import extension
|
|
||||||
from ..objects import relations
|
|
||||||
|
|
||||||
|
|
||||||
class TestExtension(BaseTestCase):
|
|
||||||
@mock.patch.object(relations.UpgradeRelationObject, "delete_relation")
|
|
||||||
def test_on_cluster_delete(self, mock_on_cluster_delete):
|
|
||||||
cluster = mock.Mock(id=42)
|
|
||||||
extension.ClusterUpgradeExtension.on_cluster_delete(cluster)
|
|
||||||
mock_on_cluster_delete.assert_called_once_with(42)
|
|
|
@ -1,249 +0,0 @@
|
||||||
# -*- 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 mock
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from nailgun import consts
|
|
||||||
from nailgun.test import base
|
|
||||||
from nailgun.utils import reverse
|
|
||||||
|
|
||||||
from . import base as tests_base
|
|
||||||
|
|
||||||
|
|
||||||
class TestClusterUpgradeCloneHandler(tests_base.BaseCloneClusterTest):
|
|
||||||
def test_clone(self):
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse("ClusterUpgradeCloneHandler",
|
|
||||||
kwargs={"cluster_id": self.src_cluster.id}),
|
|
||||||
jsonutils.dumps(self.data),
|
|
||||||
headers=self.default_headers)
|
|
||||||
body = resp.json_body
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
|
||||||
self.assertEqual(body["name"],
|
|
||||||
"cluster-clone-{0}".format(self.src_cluster.id))
|
|
||||||
self.assertEqual(body["release_id"], self.dst_release.id)
|
|
||||||
|
|
||||||
def test_clone_cluster_not_found_error(self):
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse("ClusterUpgradeCloneHandler",
|
|
||||||
kwargs={"cluster_id": 42}),
|
|
||||||
jsonutils.dumps(self.data),
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(resp.status_code, 404)
|
|
||||||
self.assertEqual(resp.json_body["message"], "Cluster not found")
|
|
||||||
|
|
||||||
def test_clone_cluster_already_in_upgrade_error(self):
|
|
||||||
self.app.post(
|
|
||||||
reverse("ClusterUpgradeCloneHandler",
|
|
||||||
kwargs={"cluster_id": self.src_cluster.id}),
|
|
||||||
jsonutils.dumps(self.data),
|
|
||||||
headers=self.default_headers)
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse("ClusterUpgradeCloneHandler",
|
|
||||||
kwargs={"cluster_id": self.src_cluster.id}),
|
|
||||||
jsonutils.dumps(self.data),
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
|
||||||
|
|
||||||
def test_clone_cluster_name_already_exists_error(self):
|
|
||||||
data = dict(self.data, name=self.src_cluster.name)
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse("ClusterUpgradeCloneHandler",
|
|
||||||
kwargs={"cluster_id": self.src_cluster.id}),
|
|
||||||
jsonutils.dumps(data),
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(resp.status_code, 409)
|
|
||||||
|
|
||||||
|
|
||||||
class TestNodeReassignHandler(base.BaseIntegrationTest):
|
|
||||||
|
|
||||||
@mock.patch('nailgun.task.task.rpc.cast')
|
|
||||||
def test_node_reassign_handler(self, mcast):
|
|
||||||
cluster = self.env.create(
|
|
||||||
cluster_kwargs={'api': False},
|
|
||||||
nodes_kwargs=[{'status': consts.NODE_STATUSES.ready}])
|
|
||||||
seed_cluster = self.env.create_cluster()
|
|
||||||
node_id = cluster.nodes[0]['id']
|
|
||||||
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': seed_cluster['id']}),
|
|
||||||
jsonutils.dumps({'node_id': node_id}),
|
|
||||||
headers=self.default_headers)
|
|
||||||
self.assertEqual(202, resp.status_code)
|
|
||||||
|
|
||||||
args, kwargs = mcast.call_args
|
|
||||||
nodes = args[1]['args']['provisioning_info']['nodes']
|
|
||||||
provisioned_uids = [int(n['uid']) for n in nodes]
|
|
||||||
self.assertEqual([node_id, ], provisioned_uids)
|
|
||||||
|
|
||||||
@mock.patch('nailgun.task.task.rpc.cast')
|
|
||||||
def test_node_reassign_handler_with_roles(self, mcast):
|
|
||||||
cluster = self.env.create(
|
|
||||||
cluster_kwargs={'api': False},
|
|
||||||
nodes_kwargs=[{'status': consts.NODE_STATUSES.ready,
|
|
||||||
'roles': ['controller']}])
|
|
||||||
node = cluster.nodes[0]
|
|
||||||
seed_cluster = self.env.create_cluster(api=False)
|
|
||||||
|
|
||||||
# NOTE(akscram): reprovision=True means that the node will be
|
|
||||||
# re-provisioned during the reassigning. This is
|
|
||||||
# a default behavior.
|
|
||||||
data = {'node_id': node.id,
|
|
||||||
'reprovision': True,
|
|
||||||
'roles': ['compute']}
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': seed_cluster.id}),
|
|
||||||
jsonutils.dumps(data),
|
|
||||||
headers=self.default_headers)
|
|
||||||
self.assertEqual(202, resp.status_code)
|
|
||||||
self.assertEqual(node.roles, [])
|
|
||||||
self.assertEqual(node.pending_roles, ['compute'])
|
|
||||||
self.assertTrue(mcast.called)
|
|
||||||
|
|
||||||
@mock.patch('nailgun.task.task.rpc.cast')
|
|
||||||
def test_node_reassign_handler_without_reprovisioning(self, mcast):
|
|
||||||
cluster = self.env.create(
|
|
||||||
cluster_kwargs={'api': False},
|
|
||||||
nodes_kwargs=[{'status': consts.NODE_STATUSES.ready,
|
|
||||||
'roles': ['controller']}])
|
|
||||||
node = cluster.nodes[0]
|
|
||||||
seed_cluster = self.env.create_cluster(api=False)
|
|
||||||
|
|
||||||
data = {'node_id': node.id,
|
|
||||||
'reprovision': False,
|
|
||||||
'roles': ['compute']}
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': seed_cluster.id}),
|
|
||||||
jsonutils.dumps(data),
|
|
||||||
headers=self.default_headers)
|
|
||||||
self.assertEqual(200, resp.status_code)
|
|
||||||
self.assertFalse(mcast.called)
|
|
||||||
self.assertEqual(node.roles, ['compute'])
|
|
||||||
|
|
||||||
def test_node_reassign_handler_no_node(self):
|
|
||||||
cluster = self.env.create_cluster()
|
|
||||||
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': cluster['id']}),
|
|
||||||
jsonutils.dumps({'node_id': 42}),
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(404, resp.status_code)
|
|
||||||
self.assertEqual("Node with id 42 was not found.",
|
|
||||||
resp.json_body['message'])
|
|
||||||
|
|
||||||
def test_node_reassing_handler_wrong_status(self):
|
|
||||||
cluster = self.env.create(
|
|
||||||
cluster_kwargs={'api': False},
|
|
||||||
nodes_kwargs=[{'status': 'discover'}])
|
|
||||||
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': cluster['id']}),
|
|
||||||
jsonutils.dumps({'node_id': cluster.nodes[0]['id']}),
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(400, resp.status_code)
|
|
||||||
self.assertRegexpMatches(resp.json_body['message'],
|
|
||||||
"^Node should be in one of statuses:")
|
|
||||||
|
|
||||||
def test_node_reassing_handler_wrong_error_type(self):
|
|
||||||
cluster = self.env.create(
|
|
||||||
cluster_kwargs={'api': False},
|
|
||||||
nodes_kwargs=[{'status': 'error',
|
|
||||||
'error_type': 'provision'}])
|
|
||||||
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': cluster['id']}),
|
|
||||||
jsonutils.dumps({'node_id': cluster.nodes[0]['id']}),
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(400, resp.status_code)
|
|
||||||
self.assertRegexpMatches(resp.json_body['message'],
|
|
||||||
"^Node should be in error state")
|
|
||||||
|
|
||||||
def test_node_reassign_handler_to_the_same_cluster(self):
|
|
||||||
cluster = self.env.create(
|
|
||||||
cluster_kwargs={'api': False},
|
|
||||||
nodes_kwargs=[{'status': 'ready'}])
|
|
||||||
|
|
||||||
cluster_id = cluster['id']
|
|
||||||
node_id = cluster.nodes[0]['id']
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': cluster_id}),
|
|
||||||
jsonutils.dumps({'node_id': node_id}),
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(400, resp.status_code)
|
|
||||||
self.assertEqual("Node {0} is already assigned to cluster {1}".
|
|
||||||
format(node_id, cluster_id),
|
|
||||||
resp.json_body['message'])
|
|
||||||
|
|
||||||
def test_node_reassign_handler_with_empty_data(self):
|
|
||||||
cluster = self.env.create_cluster(api=False)
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': cluster.id}),
|
|
||||||
"{}",
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(400, resp.status_code)
|
|
||||||
|
|
||||||
def test_node_reassign_handler_with_empty_body(self):
|
|
||||||
cluster = self.env.create_cluster(api=False)
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse('NodeReassignHandler',
|
|
||||||
kwargs={'cluster_id': cluster.id}),
|
|
||||||
"",
|
|
||||||
headers=self.default_headers,
|
|
||||||
expect_errors=True)
|
|
||||||
self.assertEqual(400, resp.status_code)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCopyVipsHandler(base.BaseIntegrationTest):
|
|
||||||
|
|
||||||
def test_copy_vips_called(self):
|
|
||||||
from ..objects import relations
|
|
||||||
|
|
||||||
orig_cluster = self.env.create_cluster(api=False)
|
|
||||||
new_cluster = self.env.create_cluster(api=False)
|
|
||||||
|
|
||||||
relations.UpgradeRelationObject.create_relation(
|
|
||||||
orig_cluster.id, new_cluster.id)
|
|
||||||
|
|
||||||
with mock.patch('nailgun.extensions.cluster_upgrade.handlers'
|
|
||||||
'.upgrade.UpgradeHelper.copy_vips') as copy_vips_mc:
|
|
||||||
resp = self.app.post(
|
|
||||||
reverse(
|
|
||||||
'CopyVIPsHandler',
|
|
||||||
kwargs={'cluster_id': new_cluster.id}
|
|
||||||
),
|
|
||||||
headers=self.default_headers,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
|
||||||
self.assertTrue(copy_vips_mc.called)
|
|
|
@ -1,48 +0,0 @@
|
||||||
# -*- 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 nailgun.test.base import BaseIntegrationTest
|
|
||||||
|
|
||||||
from .. import models
|
|
||||||
from ..objects import relations as objects
|
|
||||||
|
|
||||||
|
|
||||||
class TestUpgradeRelationObject(BaseIntegrationTest):
|
|
||||||
def test_get_and_create_relation(self):
|
|
||||||
objects.UpgradeRelationObject.create_relation(1, 2)
|
|
||||||
rel0 = objects.UpgradeRelationObject.get_cluster_relation(1)
|
|
||||||
self.assertEqual(rel0.orig_cluster_id, 1)
|
|
||||||
self.assertEqual(rel0.seed_cluster_id, 2)
|
|
||||||
rel1 = objects.UpgradeRelationObject.get_cluster_relation(2)
|
|
||||||
self.assertEqual(rel1.orig_cluster_id, 1)
|
|
||||||
self.assertEqual(rel1.seed_cluster_id, 2)
|
|
||||||
|
|
||||||
def test_is_cluster_in_upgrade(self):
|
|
||||||
objects.UpgradeRelationObject.create_relation(1, 2)
|
|
||||||
in_upgrade = objects.UpgradeRelationObject.is_cluster_in_upgrade
|
|
||||||
self.assertTrue(in_upgrade(1))
|
|
||||||
self.assertTrue(in_upgrade(2))
|
|
||||||
|
|
||||||
def test_is_cluster_not_in_upgrade(self):
|
|
||||||
self.assertEqual(self.db.query(models.UpgradeRelation).count(), 0)
|
|
||||||
in_upgrade = objects.UpgradeRelationObject.is_cluster_in_upgrade
|
|
||||||
self.assertFalse(in_upgrade(1))
|
|
||||||
self.assertFalse(in_upgrade(2))
|
|
||||||
|
|
||||||
def test_delete_relation(self):
|
|
||||||
objects.UpgradeRelationObject.create_relation(1, 2)
|
|
||||||
objects.UpgradeRelationObject.delete_relation(1)
|
|
||||||
self.assertEqual(self.db.query(models.UpgradeRelation).count(), 0)
|
|
|
@ -1,212 +0,0 @@
|
||||||
# -*- 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 copy
|
|
||||||
import six
|
|
||||||
|
|
||||||
from nailgun import consts
|
|
||||||
from nailgun.extensions.cluster_upgrade.upgrade import merge_attributes
|
|
||||||
from nailgun.extensions.network_manager.objects.serializers import \
|
|
||||||
network_configuration
|
|
||||||
|
|
||||||
from . import base as base_tests
|
|
||||||
from ..objects import adapters
|
|
||||||
from ..objects import relations
|
|
||||||
|
|
||||||
|
|
||||||
class TestUpgradeHelperCloneCluster(base_tests.BaseCloneClusterTest):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestUpgradeHelperCloneCluster, self).setUp()
|
|
||||||
|
|
||||||
self.orig_net_manager = self.src_cluster.get_network_manager()
|
|
||||||
|
|
||||||
self.serialize_nets = network_configuration.\
|
|
||||||
NeutronNetworkConfigurationSerializer.\
|
|
||||||
serialize_for_cluster
|
|
||||||
|
|
||||||
self.public_net_data = {
|
|
||||||
"cidr": "192.168.42.0/24",
|
|
||||||
"gateway": "192.168.42.1",
|
|
||||||
"ip_ranges": [["192.168.42.5", "192.168.42.11"]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_merge_attributes(self):
|
|
||||||
src_editable_attrs = {
|
|
||||||
"test":
|
|
||||||
{"metadata": "src_fake",
|
|
||||||
"key":
|
|
||||||
{"type": "text",
|
|
||||||
"value": "fake1, fake2,fake3 , fake4"},
|
|
||||||
"src_key": "src_data"
|
|
||||||
},
|
|
||||||
"repo_setup": "src_data"
|
|
||||||
}
|
|
||||||
|
|
||||||
new_editable_attrs = {
|
|
||||||
"test":
|
|
||||||
{"metadata": "new_fake",
|
|
||||||
"key":
|
|
||||||
{"type": "text_list",
|
|
||||||
"value": "fake"},
|
|
||||||
"new_key": "new_data"
|
|
||||||
},
|
|
||||||
"repo_setup": "new_data"
|
|
||||||
}
|
|
||||||
result = merge_attributes(src_editable_attrs, new_editable_attrs)
|
|
||||||
new_editable_attrs["test"]["key"]["value"] = \
|
|
||||||
[value.strip() for value in
|
|
||||||
src_editable_attrs["test"]["key"]["value"].split(',')]
|
|
||||||
self.assertEqual(result, new_editable_attrs)
|
|
||||||
|
|
||||||
def test_create_cluster_clone(self):
|
|
||||||
new_cluster = self.helper.create_cluster_clone(self.src_cluster,
|
|
||||||
self.data)
|
|
||||||
src_cluster_data = self.src_cluster.get_create_data()
|
|
||||||
new_cluster_data = new_cluster.get_create_data()
|
|
||||||
for key, value in src_cluster_data.items():
|
|
||||||
if key in ("name", "release_id"):
|
|
||||||
continue
|
|
||||||
self.assertEqual(value, new_cluster_data[key])
|
|
||||||
|
|
||||||
def test_copy_attributes(self):
|
|
||||||
new_cluster = self.helper.create_cluster_clone(self.src_cluster,
|
|
||||||
self.data)
|
|
||||||
self.assertNotEqual(self.src_cluster.generated_attrs,
|
|
||||||
new_cluster.generated_attrs)
|
|
||||||
|
|
||||||
# Do some unordinary changes
|
|
||||||
attrs = copy.deepcopy(self.src_cluster.editable_attrs)
|
|
||||||
attrs["access"]["user"]["value"] = "operator"
|
|
||||||
attrs["access"]["password"]["value"] = "secrete"
|
|
||||||
self.src_cluster.editable_attrs = attrs
|
|
||||||
|
|
||||||
self.helper.copy_attributes(self.src_cluster, new_cluster)
|
|
||||||
|
|
||||||
self.assertEqual(self.src_cluster.generated_attrs,
|
|
||||||
new_cluster.generated_attrs)
|
|
||||||
editable_attrs = self.src_cluster.editable_attrs
|
|
||||||
for section, params in six.iteritems(new_cluster.editable_attrs):
|
|
||||||
if section == "repo_setup":
|
|
||||||
continue
|
|
||||||
for key, value in six.iteritems(params):
|
|
||||||
if key == "metadata":
|
|
||||||
continue
|
|
||||||
self.assertEqual(editable_attrs[section][key]["value"],
|
|
||||||
value["value"])
|
|
||||||
|
|
||||||
def update_public_net_params(self, networks):
|
|
||||||
pub_net = self._get_pub_net(networks)
|
|
||||||
pub_net.update(self.public_net_data)
|
|
||||||
self.orig_net_manager.update(networks)
|
|
||||||
|
|
||||||
def _get_pub_net(self, networks):
|
|
||||||
return next(net for net in networks['networks'] if
|
|
||||||
net['name'] == consts.NETWORKS.public)
|
|
||||||
|
|
||||||
def test_copy_network_config(self):
|
|
||||||
new_cluster = self.helper.create_cluster_clone(self.src_cluster,
|
|
||||||
self.data)
|
|
||||||
# Do some unordinary changes to public network
|
|
||||||
nets = self.serialize_nets(self.src_cluster.cluster)
|
|
||||||
self.update_public_net_params(nets)
|
|
||||||
|
|
||||||
self.helper.copy_network_config(self.src_cluster, new_cluster)
|
|
||||||
|
|
||||||
new_nets = self.serialize_nets(new_cluster.cluster)
|
|
||||||
|
|
||||||
public_net = self._get_pub_net(new_nets)
|
|
||||||
|
|
||||||
self.assertEqual(public_net['cidr'], self.public_net_data['cidr'])
|
|
||||||
self.assertEqual(public_net['gateway'],
|
|
||||||
self.public_net_data['gateway'])
|
|
||||||
self.assertEqual(public_net['ip_ranges'],
|
|
||||||
self.public_net_data['ip_ranges'])
|
|
||||||
|
|
||||||
def test_copy_vips(self):
|
|
||||||
# save network information before node reassignment to seed cluster
|
|
||||||
# as after that no VIP will be allocated/serialized due to
|
|
||||||
# absence of assigned nodes for the source cluster
|
|
||||||
orig_nets = self.serialize_nets(self.src_cluster.cluster)
|
|
||||||
|
|
||||||
new_cluster = self.helper.clone_cluster(self.src_cluster, self.data)
|
|
||||||
|
|
||||||
# we have to move node to new cluster before VIP assignment
|
|
||||||
# because there is no point in the operation for a cluster
|
|
||||||
# w/o nodes
|
|
||||||
node = adapters.NailgunNodeAdapter(self.src_cluster.cluster.nodes[0])
|
|
||||||
self.helper.assign_node_to_cluster(node, new_cluster, node.roles, [])
|
|
||||||
|
|
||||||
self.helper.copy_vips(self.src_cluster, new_cluster)
|
|
||||||
|
|
||||||
new_nets = self.serialize_nets(new_cluster.cluster)
|
|
||||||
|
|
||||||
self.assertEqual(orig_nets["management_vip"],
|
|
||||||
new_nets["management_vip"])
|
|
||||||
self.assertEqual(orig_nets["management_vrouter_vip"],
|
|
||||||
new_nets["management_vrouter_vip"])
|
|
||||||
self.assertEqual(orig_nets["public_vip"],
|
|
||||||
new_nets["public_vip"])
|
|
||||||
self.assertEqual(orig_nets["public_vrouter_vip"],
|
|
||||||
new_nets["public_vrouter_vip"])
|
|
||||||
|
|
||||||
def test_clone_cluster(self):
|
|
||||||
self.orig_net_manager.assign_vips_for_net_groups()
|
|
||||||
new_cluster = self.helper.clone_cluster(self.src_cluster, self.data)
|
|
||||||
relation = relations.UpgradeRelationObject.get_cluster_relation(
|
|
||||||
self.src_cluster.id)
|
|
||||||
self.assertEqual(relation.orig_cluster_id, self.src_cluster.id)
|
|
||||||
self.assertEqual(relation.seed_cluster_id, new_cluster.id)
|
|
||||||
|
|
||||||
def _check_dns_and_ntp_list_values(self, new_cluster, dns_list, ntp_list):
|
|
||||||
self.assertEqual(
|
|
||||||
new_cluster.editable_attrs["external_ntp"]["ntp_list"]["value"],
|
|
||||||
ntp_list)
|
|
||||||
self.assertEqual(
|
|
||||||
new_cluster.editable_attrs["external_dns"]["dns_list"]["value"],
|
|
||||||
dns_list)
|
|
||||||
self.assertEqual(
|
|
||||||
new_cluster.editable_attrs["external_ntp"]["ntp_list"]["type"],
|
|
||||||
"text_list")
|
|
||||||
self.assertEqual(
|
|
||||||
new_cluster.editable_attrs["external_dns"]["dns_list"]["type"],
|
|
||||||
"text_list")
|
|
||||||
|
|
||||||
def test_cluster_copy_attrs_with_different_types_dns_and_ntp_lists(self):
|
|
||||||
attrs = copy.deepcopy(self.src_cluster.editable_attrs)
|
|
||||||
attrs["external_ntp"]["ntp_list"]["type"] = "text"
|
|
||||||
attrs["external_ntp"]["ntp_list"]["value"] = "1,2,3"
|
|
||||||
attrs["external_dns"]["dns_list"]["type"] = "text"
|
|
||||||
attrs["external_dns"]["dns_list"]["value"] = "4,5,6"
|
|
||||||
self.src_cluster.editable_attrs = attrs
|
|
||||||
new_cluster = self.helper.create_cluster_clone(
|
|
||||||
self.src_cluster, self.data)
|
|
||||||
self.helper.copy_attributes(self.src_cluster, new_cluster)
|
|
||||||
self._check_dns_and_ntp_list_values(
|
|
||||||
new_cluster, ["4", "5", "6"], ["1", "2", "3"])
|
|
||||||
|
|
||||||
def test_cluster_copy_attrs_with_same_types_dns_and_ntp_lists(self):
|
|
||||||
attrs = copy.deepcopy(self.src_cluster.editable_attrs)
|
|
||||||
attrs["external_ntp"]["ntp_list"]["type"] = "text_list"
|
|
||||||
attrs["external_ntp"]["ntp_list"]["value"] = ["1", "2", "3"]
|
|
||||||
attrs["external_dns"]["dns_list"]["type"] = "text_list"
|
|
||||||
attrs["external_dns"]["dns_list"]["value"] = ["4", "5", "6"]
|
|
||||||
self.src_cluster.editable_attrs = attrs
|
|
||||||
new_cluster = self.helper.create_cluster_clone(
|
|
||||||
self.src_cluster, self.data)
|
|
||||||
self.helper.copy_attributes(self.src_cluster, new_cluster)
|
|
||||||
self._check_dns_and_ntp_list_values(
|
|
||||||
new_cluster, ["4", "5", "6"], ["1", "2", "3"])
|
|
|
@ -1,230 +0,0 @@
|
||||||
# -*- 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 mock
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
from nailgun import consts
|
|
||||||
from nailgun import errors
|
|
||||||
from nailgun.settings import settings
|
|
||||||
from nailgun.test import base
|
|
||||||
|
|
||||||
from .. import validators
|
|
||||||
from . import base as tests_base
|
|
||||||
from . import EXTENSION
|
|
||||||
from ..objects import relations
|
|
||||||
|
|
||||||
|
|
||||||
class TestClusterUpgradeValidator(tests_base.BaseCloneClusterTest):
|
|
||||||
validator = validators.ClusterUpgradeValidator
|
|
||||||
|
|
||||||
def test_validate_release_upgrade(self):
|
|
||||||
self.validator.validate_release_upgrade(self.src_release,
|
|
||||||
self.dst_release)
|
|
||||||
|
|
||||||
@mock.patch.dict(settings.VERSION, {'feature_groups': []})
|
|
||||||
def test_validate_release_upgrade_deprecated_release(self):
|
|
||||||
release_511 = self.env.create_release(
|
|
||||||
operating_system=consts.RELEASE_OS.ubuntu,
|
|
||||||
version="2014.1.3-5.1.1",
|
|
||||||
state=consts.RELEASE_STATES.manageonly
|
|
||||||
)
|
|
||||||
msg = "^Upgrade to the given release \({0}\).*is deprecated and " \
|
|
||||||
"cannot be installed\.$".format(self.src_release.id)
|
|
||||||
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
|
||||||
self.validator.validate_release_upgrade(release_511,
|
|
||||||
self.src_release)
|
|
||||||
|
|
||||||
def test_validate_release_upgrade_to_older_release(self):
|
|
||||||
self.src_release.state = consts.RELEASE_STATES.available
|
|
||||||
msg = "^Upgrade to the given release \({0}\).*release is equal or " \
|
|
||||||
"lower than the release of the original cluster\.$" \
|
|
||||||
.format(self.src_release.id)
|
|
||||||
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
|
||||||
self.validator.validate_release_upgrade(self.dst_release,
|
|
||||||
self.src_release)
|
|
||||||
|
|
||||||
def test_validate_cluster_name(self):
|
|
||||||
self.validator.validate_cluster_name("cluster-42")
|
|
||||||
|
|
||||||
def test_validate_cluster_name_already_exists(self):
|
|
||||||
msg = "^Environment with this name '{0}' already exists\.$"\
|
|
||||||
.format(self.src_cluster.name)
|
|
||||||
with self.assertRaisesRegexp(errors.AlreadyExists, msg):
|
|
||||||
self.validator.validate_cluster_name(self.src_cluster.name)
|
|
||||||
|
|
||||||
def test_validate_cluster_status(self):
|
|
||||||
self.validator.validate_cluster_status(self.src_cluster)
|
|
||||||
|
|
||||||
def test_validate_cluster_status_invalid(self):
|
|
||||||
dst_cluster = self.env.create_cluster(
|
|
||||||
api=False,
|
|
||||||
release_id=self.dst_release.id,
|
|
||||||
)
|
|
||||||
relations.UpgradeRelationObject.create_relation(self.src_cluster.id,
|
|
||||||
dst_cluster.id)
|
|
||||||
msg = "^Upgrade is not possible because of the original cluster " \
|
|
||||||
"\({0}\) is already involved in the upgrade routine\.$" \
|
|
||||||
.format(self.src_cluster.id)
|
|
||||||
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
|
||||||
self.validator.validate_cluster_status(self.src_cluster)
|
|
||||||
|
|
||||||
def test_validate(self):
|
|
||||||
data = jsonutils.dumps(self.data)
|
|
||||||
self.validator.validate(data, self.src_cluster)
|
|
||||||
|
|
||||||
def test_validate_invalid_data(self):
|
|
||||||
data = "{}"
|
|
||||||
with self.assertRaises(errors.InvalidData):
|
|
||||||
self.validator.validate(data, self.src_cluster)
|
|
||||||
|
|
||||||
|
|
||||||
class TestNodeReassignValidator(base.BaseTestCase):
|
|
||||||
validator = validators.NodeReassignValidator
|
|
||||||
|
|
||||||
@mock.patch(EXTENSION + "validators.adapters.NailgunNodeAdapter."
|
|
||||||
"get_by_uid")
|
|
||||||
def test_validate_node_not_found(self, mock_gbu):
|
|
||||||
mock_gbu.return_value = None
|
|
||||||
with self.assertRaises(errors.ObjectNotFound):
|
|
||||||
self.validator.validate_node(42)
|
|
||||||
|
|
||||||
@mock.patch(EXTENSION + "validators.adapters.NailgunNodeAdapter."
|
|
||||||
"get_by_uid")
|
|
||||||
def test_validate_node_wrong_status(self, mock_gbu):
|
|
||||||
mock_gbu.return_value = mock.Mock(status='wrong_state')
|
|
||||||
with self.assertRaises(errors.InvalidData):
|
|
||||||
self.validator.validate_node(42)
|
|
||||||
|
|
||||||
@mock.patch(EXTENSION + "validators.adapters.NailgunNodeAdapter."
|
|
||||||
"get_by_uid")
|
|
||||||
def test_validate_node_wrong_error_type(self, mock_gbu):
|
|
||||||
mock_gbu.return_value = mock.Mock(status='error',
|
|
||||||
error_type='wrong')
|
|
||||||
with self.assertRaises(errors.InvalidData):
|
|
||||||
self.validator.validate_node(42)
|
|
||||||
|
|
||||||
def test_validate_node_cluster(self):
|
|
||||||
node = mock.Mock(id=42, cluster_id=42)
|
|
||||||
cluster = mock.Mock(id=42)
|
|
||||||
with self.assertRaises(errors.InvalidData):
|
|
||||||
self.validator.validate_node_cluster(node, cluster)
|
|
||||||
|
|
||||||
def test_validate_empty_data(self):
|
|
||||||
cluster = self.env.create_cluster(api=False)
|
|
||||||
node = self.env.create_node(cluster_id=cluster.id,
|
|
||||||
roles=["compute"],
|
|
||||||
status="ready")
|
|
||||||
msg = "^'node_id' is a required property"
|
|
||||||
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
|
||||||
self.validator.validate("{}", node)
|
|
||||||
|
|
||||||
def test_validate_empty_body(self):
|
|
||||||
cluster = self.env.create_cluster(api=False)
|
|
||||||
node = self.env.create_node(cluster_id=cluster.id,
|
|
||||||
roles=["compute"],
|
|
||||||
status="ready")
|
|
||||||
msg = "^Empty request received$"
|
|
||||||
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
|
||||||
self.validator.validate("", node)
|
|
||||||
|
|
||||||
|
|
||||||
class TestNodeReassignNoReinstallValidator(tests_base.BaseCloneClusterTest):
|
|
||||||
validator = validators.NodeReassignValidator
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestNodeReassignNoReinstallValidator, self).setUp()
|
|
||||||
self.dst_cluster = self.env.create_cluster(
|
|
||||||
api=False,
|
|
||||||
release_id=self.dst_release.id,
|
|
||||||
)
|
|
||||||
self.node = self.env.create_node(cluster_id=self.src_cluster.id,
|
|
||||||
roles=["compute"], status="ready")
|
|
||||||
|
|
||||||
def test_validate_defaults(self):
|
|
||||||
request = {"node_id": self.node.id}
|
|
||||||
data = jsonutils.dumps(request)
|
|
||||||
parsed = self.validator.validate(data, self.dst_cluster)
|
|
||||||
self.assertEqual(parsed, request)
|
|
||||||
self.assertEqual(self.node.roles, ['compute'])
|
|
||||||
|
|
||||||
def test_validate_with_roles(self):
|
|
||||||
request = {
|
|
||||||
"node_id": self.node.id,
|
|
||||||
"reprovision": True,
|
|
||||||
"roles": ['controller'],
|
|
||||||
}
|
|
||||||
data = jsonutils.dumps(request)
|
|
||||||
parsed = self.validator.validate(data, self.dst_cluster)
|
|
||||||
self.assertEqual(parsed, request)
|
|
||||||
|
|
||||||
def test_validate_not_unique_roles(self):
|
|
||||||
data = jsonutils.dumps({
|
|
||||||
"node_id": self.node.id,
|
|
||||||
"roles": ['compute', 'compute'],
|
|
||||||
})
|
|
||||||
msg = "has non-unique elements"
|
|
||||||
with self.assertRaisesRegexp(errors.InvalidData, msg):
|
|
||||||
self.validator.validate(data, self.dst_cluster)
|
|
||||||
|
|
||||||
def test_validate_no_reprovision_with_conflicts(self):
|
|
||||||
data = jsonutils.dumps({
|
|
||||||
"node_id": self.node.id,
|
|
||||||
"reprovision": False,
|
|
||||||
"roles": ['controller', 'compute'],
|
|
||||||
})
|
|
||||||
with self.assertRaises(errors.InvalidData) as exc:
|
|
||||||
self.validator.validate(data, self.dst_cluster)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
exc.exception.message,
|
|
||||||
"Role 'controller' in conflict with role 'compute'."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCopyVIPsValidator(base.BaseTestCase):
|
|
||||||
validator = validators.CopyVIPsValidator
|
|
||||||
|
|
||||||
def test_non_existing_relation_fail(self):
|
|
||||||
with self.assertRaises(errors.InvalidData) as cm:
|
|
||||||
self.validator.validate(data=None, cluster=None, relation=None)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
cm.exception.message,
|
|
||||||
"Relation for given cluster does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_cluster_is_not_seed(self):
|
|
||||||
cluster = self.env.create_cluster(api=False)
|
|
||||||
seed_cluster = self.env.create_cluster(api=False)
|
|
||||||
|
|
||||||
relations.UpgradeRelationObject.create_relation(
|
|
||||||
orig_cluster_id=cluster.id,
|
|
||||||
seed_cluster_id=cluster.id,
|
|
||||||
)
|
|
||||||
|
|
||||||
relation = relations.UpgradeRelationObject.get_cluster_relation(
|
|
||||||
cluster.id)
|
|
||||||
|
|
||||||
with self.assertRaises(errors.InvalidData) as cm:
|
|
||||||
self.validator.validate(data=None, cluster=seed_cluster,
|
|
||||||
relation=relation)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
cm.exception.message,
|
|
||||||
"Given cluster is not seed cluster"
|
|
||||||
)
|
|
|
@ -1,238 +0,0 @@
|
||||||
# -*- 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 collections
|
|
||||||
import copy
|
|
||||||
from distutils import version
|
|
||||||
import six
|
|
||||||
|
|
||||||
from nailgun import consts
|
|
||||||
from nailgun.extensions.network_manager.objects.serializers import \
|
|
||||||
network_configuration
|
|
||||||
from nailgun import objects
|
|
||||||
from nailgun import utils
|
|
||||||
|
|
||||||
from .objects import adapters
|
|
||||||
|
|
||||||
|
|
||||||
def merge_attributes(a, b):
|
|
||||||
"""Merge values of editable attributes.
|
|
||||||
|
|
||||||
The values of the b attributes have precedence over the values
|
|
||||||
of the a attributes.
|
|
||||||
"""
|
|
||||||
attrs = copy.deepcopy(b)
|
|
||||||
for section, pairs in six.iteritems(attrs):
|
|
||||||
if section == "repo_setup" or section not in a:
|
|
||||||
continue
|
|
||||||
a_values = a[section]
|
|
||||||
for key, values in six.iteritems(pairs):
|
|
||||||
if key != "metadata" and key in a_values:
|
|
||||||
values["value"] = a_values[key]["value"]
|
|
||||||
# NOTE: In the mitaka-9.0 release types of values dns_list and
|
|
||||||
# ntp_list were changed from 'text'
|
|
||||||
# (a string of comma-separated IP-addresses)
|
|
||||||
# to 'text_list' (a list of strings of IP-addresses).
|
|
||||||
if a_values[key]['type'] == 'text' and \
|
|
||||||
values['type'] == 'text_list':
|
|
||||||
values["value"] =\
|
|
||||||
[value.strip() for value in values['value'].split(',')]
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
def merge_nets(a, b):
|
|
||||||
new_settings = copy.deepcopy(b)
|
|
||||||
source_networks = dict((n["name"], n) for n in a["networks"])
|
|
||||||
for net in new_settings["networks"]:
|
|
||||||
if net["name"] not in source_networks:
|
|
||||||
continue
|
|
||||||
source_net = source_networks[net["name"]]
|
|
||||||
for key, value in six.iteritems(net):
|
|
||||||
if (key not in ("cluster_id", "id", "meta", "group_id") and
|
|
||||||
key in source_net):
|
|
||||||
net[key] = source_net[key]
|
|
||||||
networking_params = new_settings["networking_parameters"]
|
|
||||||
source_params = a["networking_parameters"]
|
|
||||||
for key, value in six.iteritems(networking_params):
|
|
||||||
if key not in source_params:
|
|
||||||
continue
|
|
||||||
networking_params[key] = source_params[key]
|
|
||||||
return new_settings
|
|
||||||
|
|
||||||
|
|
||||||
class UpgradeHelper(object):
|
|
||||||
network_serializers = {
|
|
||||||
consts.CLUSTER_NET_PROVIDERS.neutron:
|
|
||||||
network_configuration.NeutronNetworkConfigurationSerializer,
|
|
||||||
consts.CLUSTER_NET_PROVIDERS.nova_network:
|
|
||||||
network_configuration.NovaNetworkConfigurationSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def clone_cluster(cls, orig_cluster, data):
|
|
||||||
from .objects import relations
|
|
||||||
|
|
||||||
new_cluster = cls.create_cluster_clone(orig_cluster, data)
|
|
||||||
cls.copy_attributes(orig_cluster, new_cluster)
|
|
||||||
cls.copy_network_config(orig_cluster, new_cluster)
|
|
||||||
relations.UpgradeRelationObject.create_relation(orig_cluster.id,
|
|
||||||
new_cluster.id)
|
|
||||||
return new_cluster
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_cluster_clone(cls, orig_cluster, data):
|
|
||||||
create_data = orig_cluster.get_create_data()
|
|
||||||
create_data["name"] = data["name"]
|
|
||||||
create_data["release_id"] = data["release_id"]
|
|
||||||
new_cluster = adapters.NailgunClusterAdapter.create(create_data)
|
|
||||||
return new_cluster
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def copy_attributes(cls, orig_cluster, new_cluster):
|
|
||||||
# TODO(akscram): Attributes should be copied including
|
|
||||||
# borderline cases when some parameters are
|
|
||||||
# renamed or moved into plugins. Also, we should
|
|
||||||
# to keep special steps in copying of parameters
|
|
||||||
# that know how to translate parameters from one
|
|
||||||
# version to another. A set of this kind of steps
|
|
||||||
# should define an upgrade path of a particular
|
|
||||||
# cluster.
|
|
||||||
new_cluster.generated_attrs = utils.dict_merge(
|
|
||||||
new_cluster.generated_attrs,
|
|
||||||
orig_cluster.generated_attrs)
|
|
||||||
new_cluster.editable_attrs = merge_attributes(
|
|
||||||
orig_cluster.editable_attrs,
|
|
||||||
new_cluster.editable_attrs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def transform_vips_for_net_groups_70(cls, vips):
|
|
||||||
"""Rename or remove types of VIPs for 7.0 network groups.
|
|
||||||
|
|
||||||
This method renames types of VIPs from older releases (<7.0) to
|
|
||||||
be compatible with network groups of the 7.0 release according
|
|
||||||
to the rules:
|
|
||||||
|
|
||||||
management: haproxy -> management
|
|
||||||
public: haproxy -> public
|
|
||||||
public: vrouter -> vrouter_pub
|
|
||||||
|
|
||||||
Note, that in the result VIPs are present only those IPs that
|
|
||||||
correspond to the given rules.
|
|
||||||
"""
|
|
||||||
rename_vip_rules = {
|
|
||||||
"management": {
|
|
||||||
"haproxy": "management",
|
|
||||||
"vrouter": "vrouter",
|
|
||||||
},
|
|
||||||
"public": {
|
|
||||||
"haproxy": "public",
|
|
||||||
"vrouter": "vrouter_pub",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
renamed_vips = collections.defaultdict(dict)
|
|
||||||
for ng_name, vips in six.iteritems(vips):
|
|
||||||
ng_vip_rules = rename_vip_rules[ng_name]
|
|
||||||
for vip_name, vip_addr in six.iteritems(vips):
|
|
||||||
if vip_name not in ng_vip_rules:
|
|
||||||
continue
|
|
||||||
new_vip_name = ng_vip_rules[vip_name]
|
|
||||||
renamed_vips[ng_name][new_vip_name] = vip_addr
|
|
||||||
return renamed_vips
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def copy_network_config(cls, orig_cluster, new_cluster):
|
|
||||||
nets_serializer = cls.network_serializers[orig_cluster.net_provider]
|
|
||||||
nets = merge_nets(
|
|
||||||
nets_serializer.serialize_for_cluster(orig_cluster.cluster),
|
|
||||||
nets_serializer.serialize_for_cluster(new_cluster.cluster))
|
|
||||||
|
|
||||||
new_net_manager = new_cluster.get_network_manager()
|
|
||||||
|
|
||||||
new_net_manager.update(nets)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def copy_vips(cls, orig_cluster, new_cluster):
|
|
||||||
orig_net_manager = orig_cluster.get_network_manager()
|
|
||||||
new_net_manager = new_cluster.get_network_manager()
|
|
||||||
|
|
||||||
vips = {}
|
|
||||||
assigned_vips = orig_net_manager.get_assigned_vips()
|
|
||||||
for ng_name in (consts.NETWORKS.public, consts.NETWORKS.management):
|
|
||||||
vips[ng_name] = assigned_vips[ng_name]
|
|
||||||
# NOTE(akscram): In the 7.0 release was introduced networking
|
|
||||||
# templates that use the vip_name column as
|
|
||||||
# unique names of VIPs.
|
|
||||||
if version.LooseVersion(orig_cluster.release.environment_version) < \
|
|
||||||
version.LooseVersion("7.0"):
|
|
||||||
vips = cls.transform_vips_for_net_groups_70(vips)
|
|
||||||
new_net_manager.assign_given_vips_for_net_groups(vips)
|
|
||||||
new_net_manager.assign_vips_for_net_groups()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node_roles(cls, reprovision, current_roles, given_roles):
|
|
||||||
"""Return roles depending on the reprovisioning status.
|
|
||||||
|
|
||||||
In case the node should be re-provisioned, only pending roles
|
|
||||||
should be set, otherwise for an already provisioned and deployed
|
|
||||||
node only actual roles should be set. In the both case the
|
|
||||||
given roles will have precedence over the existing.
|
|
||||||
|
|
||||||
:param reprovision: boolean, if set to True then the node should
|
|
||||||
be re-provisioned
|
|
||||||
:param current_roles: a list of current roles of the node
|
|
||||||
:param given_roles: a list of roles that should be assigned to
|
|
||||||
the node
|
|
||||||
:returns: a tuple of a list of roles and a list of pending roles
|
|
||||||
that will be assigned to the node
|
|
||||||
"""
|
|
||||||
roles_to_assign = given_roles if given_roles else current_roles
|
|
||||||
if reprovision:
|
|
||||||
roles, pending_roles = [], roles_to_assign
|
|
||||||
else:
|
|
||||||
roles, pending_roles = roles_to_assign, []
|
|
||||||
return roles, pending_roles
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def assign_node_to_cluster(cls, node, seed_cluster, roles, pending_roles):
|
|
||||||
orig_cluster = adapters.NailgunClusterAdapter.get_by_uid(
|
|
||||||
node.cluster_id)
|
|
||||||
|
|
||||||
orig_manager = orig_cluster.get_network_manager()
|
|
||||||
|
|
||||||
netgroups_id_mapping = cls.get_netgroups_id_mapping(
|
|
||||||
orig_cluster, seed_cluster)
|
|
||||||
|
|
||||||
node.update_cluster_assignment(seed_cluster, roles, pending_roles)
|
|
||||||
objects.Node.set_netgroups_ids(node, netgroups_id_mapping)
|
|
||||||
|
|
||||||
if not seed_cluster.network_template:
|
|
||||||
orig_manager.set_nic_assignment_netgroups_ids(
|
|
||||||
node, netgroups_id_mapping)
|
|
||||||
orig_manager.set_bond_assignment_netgroups_ids(
|
|
||||||
node, netgroups_id_mapping)
|
|
||||||
|
|
||||||
node.add_pending_change(consts.CLUSTER_CHANGES.interfaces)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_netgroups_id_mapping(self, orig_cluster, seed_cluster):
|
|
||||||
orig_ng = orig_cluster.get_network_groups()
|
|
||||||
seed_ng = seed_cluster.get_network_groups()
|
|
||||||
|
|
||||||
seed_ng_dict = dict((ng.name, ng.id) for ng in seed_ng)
|
|
||||||
mapping = dict((ng.id, seed_ng_dict[ng.name]) for ng in orig_ng)
|
|
||||||
mapping[orig_cluster.get_admin_network_group().id] = \
|
|
||||||
seed_cluster.get_admin_network_group().id
|
|
||||||
return mapping
|
|
|
@ -1,164 +0,0 @@
|
||||||
# -*- 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 nailgun.api.v1.validators import assignment
|
|
||||||
from nailgun.api.v1.validators import base
|
|
||||||
from nailgun import consts
|
|
||||||
from nailgun import errors
|
|
||||||
from nailgun import objects
|
|
||||||
|
|
||||||
from .objects import adapters
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterUpgradeValidator(base.BasicValidator):
|
|
||||||
schema = {
|
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
||||||
"title": "Start upgrade procedure for a cluster",
|
|
||||||
"description": "Serialized parameters to upgrade a cluster.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {"type": "string"},
|
|
||||||
"release_id": {"type": "number"},
|
|
||||||
},
|
|
||||||
"required": ["name", "release_id"],
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, data, cluster):
|
|
||||||
cluster = adapters.NailgunClusterAdapter(cluster)
|
|
||||||
data = super(ClusterUpgradeValidator, cls).validate(data)
|
|
||||||
cls.validate_schema(data, cls.schema)
|
|
||||||
cls.validate_cluster_status(cluster)
|
|
||||||
cls.validate_cluster_name(data["name"])
|
|
||||||
release = adapters.NailgunReleaseAdapter.get_by_uid(
|
|
||||||
data["release_id"], fail_if_not_found=True)
|
|
||||||
cls.validate_release_upgrade(cluster.release, release)
|
|
||||||
return data
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate_release_upgrade(cls, orig_release, new_release):
|
|
||||||
if not objects.Release.is_deployable(new_release):
|
|
||||||
raise errors.InvalidData(
|
|
||||||
"Upgrade to the given release ({0}) is not possible because "
|
|
||||||
"this release is deprecated and cannot be installed."
|
|
||||||
.format(new_release.id),
|
|
||||||
log_message=True)
|
|
||||||
if orig_release >= new_release:
|
|
||||||
raise errors.InvalidData(
|
|
||||||
"Upgrade to the given release ({0}) is not possible because "
|
|
||||||
"this release is equal or lower than the release of the "
|
|
||||||
"original cluster.".format(new_release.id),
|
|
||||||
log_message=True)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate_cluster_name(cls, cluster_name):
|
|
||||||
clusters = objects.ClusterCollection.filter_by(None,
|
|
||||||
name=cluster_name)
|
|
||||||
if clusters.first():
|
|
||||||
raise errors.AlreadyExists(
|
|
||||||
"Environment with this name '{0}' already exists."
|
|
||||||
.format(cluster_name),
|
|
||||||
log_message=True)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate_cluster_status(cls, cluster):
|
|
||||||
from .objects.relations import UpgradeRelationObject
|
|
||||||
|
|
||||||
if UpgradeRelationObject.is_cluster_in_upgrade(cluster.id):
|
|
||||||
raise errors.InvalidData(
|
|
||||||
"Upgrade is not possible because of the original cluster ({0})"
|
|
||||||
" is already involved in the upgrade routine."
|
|
||||||
.format(cluster.id),
|
|
||||||
log_message=True)
|
|
||||||
|
|
||||||
|
|
||||||
class NodeReassignValidator(assignment.NodeAssignmentValidator):
|
|
||||||
schema = {
|
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
||||||
"title": "Assign Node Parameters",
|
|
||||||
"description": "Serialized parameters to assign node",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"node_id": {"type": "number"},
|
|
||||||
"reprovision": {"type": "boolean", "default": True},
|
|
||||||
"roles": {"type": "array",
|
|
||||||
"items": {"type": "string"},
|
|
||||||
"uniqueItems": True},
|
|
||||||
},
|
|
||||||
"required": ["node_id"],
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, data, cluster):
|
|
||||||
parsed = super(NodeReassignValidator, cls).validate(data)
|
|
||||||
cls.validate_schema(parsed, cls.schema)
|
|
||||||
|
|
||||||
node = cls.validate_node(parsed['node_id'])
|
|
||||||
cls.validate_node_cluster(node, cluster)
|
|
||||||
|
|
||||||
roles = parsed.get('roles', [])
|
|
||||||
if roles:
|
|
||||||
cls.validate_roles(cluster, roles)
|
|
||||||
else:
|
|
||||||
cls.validate_roles(cluster, node.roles)
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate_node(cls, node_id):
|
|
||||||
node = adapters.NailgunNodeAdapter.get_by_uid(node_id)
|
|
||||||
|
|
||||||
if not node:
|
|
||||||
raise errors.ObjectNotFound("Node with id {0} was not found.".
|
|
||||||
format(node_id), log_message=True)
|
|
||||||
|
|
||||||
# node can go to error state while upgrade process
|
|
||||||
allowed_statuses = (consts.NODE_STATUSES.ready,
|
|
||||||
consts.NODE_STATUSES.provisioned,
|
|
||||||
consts.NODE_STATUSES.error)
|
|
||||||
if node.status not in allowed_statuses:
|
|
||||||
raise errors.InvalidData("Node should be in one of statuses: {0}."
|
|
||||||
" Currently node has {1} status.".
|
|
||||||
format(allowed_statuses, node.status),
|
|
||||||
log_message=True)
|
|
||||||
if node.status == consts.NODE_STATUSES.error and\
|
|
||||||
node.error_type != consts.NODE_ERRORS.deploy:
|
|
||||||
raise errors.InvalidData("Node should be in error state only with"
|
|
||||||
"deploy error type. Currently error type"
|
|
||||||
" of node is {0}".format(node.error_type),
|
|
||||||
log_message=True)
|
|
||||||
return node
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate_node_cluster(cls, node, cluster):
|
|
||||||
if node.cluster_id == cluster.id:
|
|
||||||
raise errors.InvalidData("Node {0} is already assigned to cluster"
|
|
||||||
" {1}".format(node.id, cluster.id),
|
|
||||||
log_message=True)
|
|
||||||
|
|
||||||
|
|
||||||
class CopyVIPsValidator(base.BasicValidator):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, data, cluster, relation):
|
|
||||||
if relation is None:
|
|
||||||
raise errors.InvalidData(
|
|
||||||
"Relation for given cluster does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
if cluster.id != relation.seed_cluster_id:
|
|
||||||
raise errors.InvalidData("Given cluster is not seed cluster")
|
|
||||||
|
|
||||||
return data
|
|
|
@ -88,8 +88,6 @@ if __name__ == "__main__":
|
||||||
'delete_expired_oswl_entries'),
|
'delete_expired_oswl_entries'),
|
||||||
],
|
],
|
||||||
'nailgun.extensions': [
|
'nailgun.extensions': [
|
||||||
('cluster_upgrade = nailgun.extensions.cluster_upgrade'
|
|
||||||
'.extension:ClusterUpgradeExtension'),
|
|
||||||
('volume_manager = nailgun.extensions.volume_manager'
|
('volume_manager = nailgun.extensions.volume_manager'
|
||||||
'.extension:VolumeManagerExtension'),
|
'.extension:VolumeManagerExtension'),
|
||||||
('network_manager = nailgun.extensions.network_manager'
|
('network_manager = nailgun.extensions.network_manager'
|
||||||
|
|
Loading…
Reference in New Issue