Raise error once there's no deployment tasks in db

Added new exception 'NoDeploymentTasks' which is raised when user
is trying to manually deploy cluster whose release has empty
'deployment_tasks' and 'fuel_version' >= 6.1

Added loading of fake deployment tasks in both test Environment class
and 'loaddefault' command - so it also works in fake mode.

Change-Id: Ibaee6912cfbca27dbb35c4f9c09feb74c81b02a1
Closes-Bug: #1448066
This commit is contained in:
Sylwester Brzeczkowski 2015-05-07 08:53:54 +02:00
parent 0c66198156
commit 6b6219e74e
17 changed files with 246 additions and 27 deletions

View File

@ -92,8 +92,8 @@ def load_db_parsers(subparsers):
)
subparsers.add_parser(
'loaddefault',
help='load data from default fixtures '
'(settings.FIXTURES_TO_IPLOAD)'
help='load data from default fixtures (settings.FIXTURES_TO_UPLOAD) '
'and apply fake deployment tasks for all releases in database'
)
@ -173,12 +173,23 @@ def action_loaddata(params):
logger.info("Done")
def action_loadfakedeploymenttasks(params):
from nailgun.db.sqlalchemy import fixman
from nailgun.logger import logger
logger.info("Applying fake deployment tasks to all releases...")
fixman.load_fake_deployment_tasks()
logger.info("Done")
def action_loaddefault(params):
from nailgun.db.sqlalchemy import fixman
from nailgun.logger import logger
logger.info("Uploading fixture...")
fixman.upload_fixtures()
logger.info("Applying fake deployment tasks to all releases...")
fixman.load_fake_deployment_tasks()
logger.info("Done")
@ -270,7 +281,7 @@ def action_run(params):
if params.authentication_method:
auth_method = params.authentication_method
settings.AUTH.update({'AUTHENTICATION_METHOD' : auth_method})
settings.AUTH.update({'AUTHENTICATION_METHOD': auth_method})
if params.config_file:
settings.update_from_file(params.config_file)

View File

@ -163,7 +163,6 @@ class BaseHandler(object):
try:
data = kwargs.pop('data', web.data())
method = validate_method or cls.validator.validate
valid_data = method(data, **kwargs)
except (
errors.InvalidInterfacesInfo,
@ -185,6 +184,7 @@ class BaseHandler(object):
except (
errors.InvalidData,
errors.NodeOffline,
errors.NoDeploymentTasks,
errors.UnavailableRelease,
errors.CannotDelete
) as exc:
@ -547,6 +547,7 @@ class DeferredTaskHandler(BaseHandler):
raise self.http(409, exc.message)
except (
errors.DeploymentNotRunning,
errors.NoDeploymentTasks,
errors.WrongNodeStatus,
errors.UnavailableRelease,
) as exc:

View File

@ -274,6 +274,8 @@ class BaseDeploySelectedNodes(SelectedNodesBase):
self.checked_data(self.validator.validate_nodes_to_deploy,
nodes=nodes_to_deploy, cluster_id=cluster.id)
self.checked_data(self.validator.validate_release, cluster=cluster)
return nodes_to_deploy

View File

@ -19,6 +19,7 @@ from jsonschema.exceptions import ValidationError
from oslo.serialization import jsonutils
from nailgun.errors import errors
from nailgun import objects
class BasicValidator(object):
@ -91,6 +92,21 @@ class BasicValidator(object):
# about internal attributes with error description.
raise errors.InvalidData(str(exc))
@classmethod
def validate_release(cls, data=None, cluster=None):
"""Validate if deployment tasks are present in db
:param cluster: Cluster instance
:raises NoDeploymentTasks:
"""
if (cluster and objects.Release.is_granular_enabled(cluster.release)
and not objects.Cluster.get_deployment_tasks(cluster)):
raise errors.NoDeploymentTasks(
"Deployment tasks not found for '{0}' release in the "
"database. Please upload them. If you're operating "
"from Fuel Master node, please check '/etc/puppet' "
"directory.".format(cluster.release.name))
class BaseDefferedTaskValidator(BasicValidator):

View File

@ -217,6 +217,7 @@ class ClusterChangesValidator(BaseDefferedTaskValidator):
@classmethod
def validate(cls, cluster):
cls.validate_release(cluster=cluster)
ProvisionSelectedNodesValidator.validate_provision(None, cluster)

View File

@ -345,7 +345,7 @@ class DeploySelectedNodesValidator(NodesFilterValidator):
in proper state
:param data: raw json data, usually web.data(). Is not used here
and is needed for mantaining consistency of data validating logic
and is needed for maintaining consistency of data validating logic
:param nodes: list of node objects state of which to be checked
:param cluster_id: id of the cluster for which operation is performed
"""

View File

@ -39,6 +39,25 @@ def capitalize_model_name(model_name):
return ''.join(map(lambda s: s.capitalize(), model_name.split('_')))
def load_fake_deployment_tasks(apply_to_db=True, commit=True):
"""Load fake deployment tasks
:param apply_to_db: if True applying to all releases in db
:param commit: boolean
"""
fxtr_path = os.path.join(get_base_fixtures_path(), 'deployment_tasks.yaml')
with open(fxtr_path) as f:
deployment_tasks = yaml.load(f)
if apply_to_db:
for rel in db().query(models.Release).all():
rel.deployment_tasks = deployment_tasks
if commit:
db().commit()
else:
return deployment_tasks
def template_fixture(fileobj, **kwargs):
if not kwargs.get('settings'):
kwargs["settings"] = settings
@ -183,11 +202,19 @@ def upload_fixture(fileobj, loader=None):
db().commit()
def upload_fixtures():
fixtures_paths = [
def get_base_fixtures_path():
return os.path.join(os.path.dirname(__file__), '..', '..', 'fixtures')
def get_all_fixtures_paths():
return [
'/etc/nailgun/fixtures',
os.path.join(os.path.dirname(__file__), '..', '..', 'fixtures')
get_base_fixtures_path(),
]
def upload_fixtures():
fixtures_paths = get_all_fixtures_paths()
for orig_path in settings.FIXTURES_TO_UPLOAD:
if os.path.isabs(orig_path):
path = orig_path

View File

@ -38,6 +38,7 @@ default_messages = {
"CheckBeforeDeploymentError": "Pre-Deployment check wasn't successful",
"DeploymentAlreadyStarted": "Deployment already started",
"DeploymentNotRunning": "Deployment is not running",
"NoDeploymentTasks": "Deployment tasks not found for specific release in the database",
"DeletionAlreadyStarted": "Environment removal already started",
"StopAlreadyRunning": "Stopping deployment already initiated",
"FailedProvisioning": "Failed to start provisioning",

View File

@ -0,0 +1,91 @@
- id: deploy_start
type: stage
- id: deploy_end
type: stage
requires: [deploy_start]
- id: primary-controller
type: group
role: [primary-controller]
required_for: [deploy_end]
requires: [deploy_start]
parameters:
strategy:
type: one_by_one
- id: controller
type: group
role: [controller]
requires: [primary-controller]
required_for: [deploy_end]
parameters:
strategy:
type: parallel
amount: 6
- id: cinder
type: group
role: [cinder]
requires: [controller]
required_for: [deploy_end]
parameters:
strategy:
type: parallel
- id: compute
type: group
role: [compute]
requires: [controller]
required_for: [deploy_end]
parameters:
strategy:
type: parallel
- id: zabbix-server
type: group
role: [zabbix-server]
required_for: [deploy_end]
requires: [deploy_start]
parameters:
strategy:
type: one_by_one
- id: mongo
type: group
role: [mongo]
requires: [zabbix-server]
required_for: [deploy_end, primary-controller, controller]
parameters:
strategy:
type: parallel
- id: primary-mongo
type: group
role: [primary-mongo]
requires: [mongo]
required_for: [deploy_end, primary-controller, controller]
parameters:
strategy:
type: one_by_one
- id: ceph-osd
type: group
role: [ceph-osd]
requires: [controller]
required_for: [deploy_end]
parameters:
strategy:
type: parallel
- id: base-os
type: group
role: [base-os]
required_for: [deploy_end]
parameters:
strategy:
type: parallel
- id: deploy_legacy
type: puppet
groups: [primary-controller, controller,
cinder, compute, ceph-osd,
zabbix-server, primary-mongo, mongo]
required_for: [deploy_end]
requires: [deploy_start]
parameters:
puppet_manifest: /etc/puppet/manifests/site.pp
puppet_modules: /etc/puppet/modules
timeout: 3600

View File

@ -17,7 +17,7 @@
"""
Release object and collection
"""
from distutils.version import StrictVersion
from sqlalchemy import not_
import yaml
@ -149,6 +149,16 @@ class Release(NailgunObject):
return True
return instance.is_deployable
@classmethod
def is_granular_enabled(cls, instance):
"""Check if granular deployment is available for release
:param instance: a Release instance
:returns: boolean
"""
return (StrictVersion(instance.fuel_version) >=
StrictVersion(consts.FUEL_GRANULAR_DEPLOY))
@classmethod
def get_deployment_tasks(cls, instance):
"""Get deployment graph based on release version."""
@ -157,8 +167,8 @@ class Release(NailgunObject):
return instance.deployment_tasks
elif env_version.startswith('5.0'):
return yaml.load(graph_configuration.DEPLOYMENT_50)
else:
return yaml.load(graph_configuration.DEPLOYMENT_CURRENT)
elif env_version.startswith('5.1') or env_version.startswith('6.0'):
return yaml.load(graph_configuration.DEPLOYMENT_51_60)
class ReleaseCollection(NailgunCollection):

View File

@ -16,7 +16,7 @@
#(dshulyak) temporary, this config will be moved to fuel-library
#until we will stabilize our api
DEPLOYMENT_CURRENT = """
DEPLOYMENT_51_60 = """
- id: deploy_start
type: stage

View File

@ -15,7 +15,6 @@
# under the License.
from copy import deepcopy
from distutils.version import StrictVersion
import netaddr
import requests
@ -125,10 +124,9 @@ class DeploymentTask(object):
:param cluster: Cluster db object
:returns: string - deploy/granular_deploy
"""
if (StrictVersion(cluster.release.fuel_version) <
StrictVersion(consts.FUEL_GRANULAR_DEPLOY)):
return 'deploy'
return 'granular_deploy'
if objects.Release.is_granular_enabled(cluster.release):
return 'granular_deploy'
return 'deploy'
@classmethod
def message(cls, task, nodes, deployment_tasks=None):

View File

@ -49,6 +49,7 @@ from nailgun.db import syncdb
from nailgun.logger import logger
from nailgun.db.sqlalchemy.fixman import load_fake_deployment_tasks
from nailgun.db.sqlalchemy.fixman import load_fixture
from nailgun.db.sqlalchemy.fixman import upload_fixture
from nailgun.db.sqlalchemy.models import NodeAttributes
@ -150,8 +151,11 @@ class EnvironmentManager(object):
'roles': self.get_default_roles(),
})
if kwargs:
release_data.update(kwargs)
if kwargs.get('deployment_tasks') is None:
kwargs['deployment_tasks'] = \
load_fake_deployment_tasks(apply_to_db=False)
release_data.update(kwargs)
if api:
resp = self.app.post(
reverse('ReleaseCollectionHandler'),

View File

@ -1639,6 +1639,25 @@ class TestHandlers(BaseIntegrationTest):
self.assertEqual(resp.status_code, 400)
self.assertRegexpMatches(resp.body, 'Release .* is unavailable')
def test_occurs_error_no_deployment_tasks_for_release(self):
self.env.create(
nodes_kwargs=[
{'roles': ['controller'], 'pending_addition': True}],
release_kwargs={
'version': "2014.2.2-6.1",
'deployment_tasks': [],
},
)
resp = self.app.put(
reverse(
'ClusterChangesHandler',
kwargs={'cluster_id': self.env.clusters[0].id}),
headers=self.default_headers,
expect_errors=True)
self.assertEqual(resp.status_code, 400)
self.assertIn("Deployment tasks not found", resp.body)
@fake_tasks(override_state={"progress": 100, "status": "ready"})
def test_enough_osds_for_ceph(self):
cluster = self.env.create(

View File

@ -15,10 +15,11 @@
# under the License.
import cStringIO
import os
from oslo.serialization import jsonutils
import yaml
from nailgun.db.sqlalchemy.fixman import upload_fixture
from nailgun.db.sqlalchemy import fixman
from nailgun.db.sqlalchemy.models import Node
from nailgun.db.sqlalchemy.models import Release
from nailgun.test.base import BaseIntegrationTest
@ -32,6 +33,17 @@ class TestFixture(BaseIntegrationTest):
check = self.db.query(Node).all()
self.assertEqual(len(list(check)), 8)
def test_load_fake_deployment_tasks(self):
fxtr_path = os.path.join(fixman.get_base_fixtures_path(),
'deployment_tasks.yaml')
with open(fxtr_path) as f:
deployment_tasks = yaml.load(f)
fixman.load_fake_deployment_tasks()
for rel in self.db.query(Release).all():
self.assertEqual(rel.deployment_tasks, deployment_tasks)
def test_json_fixture(self):
data = '''[{
"pk": 2,
@ -44,7 +56,7 @@ class TestFixture(BaseIntegrationTest):
}
}]'''
upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
fixman.upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
check = self.db.query(Release).filter(
Release.name == u"JSONFixtureRelease"
)
@ -66,7 +78,7 @@ class TestFixture(BaseIntegrationTest):
operating_system: CentOS
'''
upload_fixture(cStringIO.StringIO(data), loader=yaml)
fixman.upload_fixture(cStringIO.StringIO(data), loader=yaml)
check = self.db.query(Release).filter(
Release.name == u"YAMLFixtureRelease"
)
@ -88,7 +100,7 @@ class TestFixture(BaseIntegrationTest):
"roles": ["controller", "compute", "cinder", "ceph-osd"]
}
}]'''
upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
fixman.upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
rel = self.db.query(Release).filter(
Release.name == u"CustomFixtureRelease1"
).all()
@ -107,7 +119,7 @@ class TestFixture(BaseIntegrationTest):
"roles": ["compute", "ceph-osd", "controller", "cinder"]
}
}]'''
upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
fixman.upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
rel = self.db.query(Release).filter(
Release.name == u"CustomFixtureRelease2"
).all()
@ -126,7 +138,7 @@ class TestFixture(BaseIntegrationTest):
"roles": ["compute", "cinder", "controller", "cinder"]
}
}]'''
upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
fixman.upload_fixture(cStringIO.StringIO(data), loader=jsonutils)
rel = self.db.query(Release).filter(
Release.name == u"CustomFixtureRelease3"
).all()

View File

@ -290,6 +290,33 @@ class TestSelectedNodesAction(BaseSelectedNodesTest):
"[{0}] marked for deletion".format(marked_for_deletion.id),
resp.body)
@fake_tasks(fake_rpc=False, mock_rpc=False)
@patch('nailgun.task.task.rpc.cast')
def test_deployment_of_node_no_deployment_tasks(self, mcast):
controller_nodes = [
n for n in self.cluster.nodes
if "controller" in n.roles
]
self.emulate_nodes_provisioning(controller_nodes)
node_to_deploy = self.cluster.nodes[0]
deploy_action_url = self.make_action_url(
"DeploySelectedNodes",
[node_to_deploy.uid]
)
# overwriting default made in EnvironmentManager
self.cluster.release.deployment_tasks = []
resp = self.send_put(deploy_action_url)
resp_msg = jsonutils.loads(resp.body)['message']
self.assertFalse(mcast.called)
self.assertEqual(resp.status_code, 400)
self.assertIn("Deployment tasks not found", resp_msg)
self.assertIn(self.cluster.release.name, resp_msg)
class TestDeploymentHandlerSkipTasks(BaseSelectedNodesTest):
@ -353,7 +380,6 @@ class TestDeployMethodVersioning(BaseSelectedNodesTest):
self.node_uids
)
self.send_put(action_url)
deployment_method = mcast.call_args_list[0][0][1]['method']
self.assertEqual(deployment_method, method)

View File

@ -224,7 +224,7 @@ class TestLegacyGraphSerialized(base.BaseTestCase):
super(TestLegacyGraphSerialized, self).setUp()
self.cluster = mock.Mock()
self.cluster.deployment_tasks = yaml.load(
graph_configuration.DEPLOYMENT_CURRENT)
graph_configuration.DEPLOYMENT_51_60)
self.graph = deployment_graph.AstuteGraph(self.cluster)
def test_serialized_with_tasks_and_priorities(self):