257 lines
8.2 KiB
Python
257 lines
8.2 KiB
Python
# -*- 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.
|
|
|
|
"""
|
|
Release object and collection
|
|
"""
|
|
import copy
|
|
|
|
import six
|
|
from sqlalchemy import not_
|
|
|
|
from nailgun import consts
|
|
|
|
from nailgun.objects.serializers import release as release_serializer
|
|
|
|
from nailgun.db import db
|
|
|
|
from nailgun.db.sqlalchemy import models
|
|
|
|
from nailgun.objects import NailgunCollection
|
|
from nailgun.objects import NailgunObject
|
|
|
|
from nailgun.settings import settings
|
|
|
|
|
|
class ReleaseOrchestratorData(NailgunObject):
|
|
"""ReleaseOrchestratorData object
|
|
"""
|
|
|
|
#: SQLAlchemy model
|
|
model = models.ReleaseOrchestratorData
|
|
|
|
#: Serializer for ReleaseOrchestratorData
|
|
serializer = release_serializer.ReleaseOrchestratorDataSerializer
|
|
|
|
#: JSON schema
|
|
schema = {
|
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
"title": "ReleaseOrchestratorData",
|
|
"description": "Serialized ReleaseOrchestratorData object",
|
|
"type": "object",
|
|
"required": [
|
|
"release_id"
|
|
],
|
|
"properties": {
|
|
"id": {"type": "number"},
|
|
"release_id": {"type": "number"},
|
|
"repo_metadata": {"type": "object"},
|
|
"puppet_manifests_source": {"type": "string"},
|
|
"puppet_modules_source": {"type": "string"}
|
|
}
|
|
}
|
|
|
|
@classmethod
|
|
def create(cls, data):
|
|
rendered_data = cls.render_data(data)
|
|
return super(ReleaseOrchestratorData, cls).create(rendered_data)
|
|
|
|
@classmethod
|
|
def update(cls, instance, data):
|
|
rendered_data = cls.render_data(data)
|
|
return super(ReleaseOrchestratorData, cls).update(
|
|
instance, rendered_data)
|
|
|
|
@classmethod
|
|
def render_data(cls, data):
|
|
# Actually, we don't have any reason to make copy at least now.
|
|
# The only reason I want to make copy is to be sure that changed
|
|
# data don't broke something somewhere in the code, since
|
|
# without a copy our changes affect entire application.
|
|
rendered_data = copy.deepcopy(data)
|
|
|
|
# create context for rendering
|
|
release = Release.get_by_uid(rendered_data['release_id'])
|
|
context = {
|
|
'MASTER_IP': settings.MASTER_IP,
|
|
'OPENSTACK_VERSION': release.version}
|
|
|
|
# render all the paths
|
|
repo_metadata = {}
|
|
for key, value in six.iteritems(rendered_data['repo_metadata']):
|
|
formatted_key = cls.render_path(key, context)
|
|
repo_metadata[formatted_key] = cls.render_path(value, context)
|
|
rendered_data['repo_metadata'] = repo_metadata
|
|
|
|
rendered_data['puppet_manifests_source'] = \
|
|
cls.render_path(rendered_data.get(
|
|
'puppet_manifests_source', 'default'), context)
|
|
|
|
rendered_data['puppet_modules_source'] = \
|
|
cls.render_path(rendered_data.get(
|
|
'puppet_modules_source', 'default'), context)
|
|
|
|
return rendered_data
|
|
|
|
@classmethod
|
|
def render_path(cls, path, context):
|
|
return path.format(**context)
|
|
|
|
|
|
class Release(NailgunObject):
|
|
"""Release object
|
|
"""
|
|
|
|
#: SQLAlchemy model for Release
|
|
model = models.Release
|
|
|
|
#: Serializer for Release
|
|
serializer = release_serializer.ReleaseSerializer
|
|
|
|
#: Release JSON schema
|
|
schema = {
|
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
"title": "Release",
|
|
"description": "Serialized Release object",
|
|
"type": "object",
|
|
"required": [
|
|
"name",
|
|
"operating_system"
|
|
],
|
|
"properties": {
|
|
"id": {"type": "number"},
|
|
"name": {"type": "string"},
|
|
"version": {"type": "string"},
|
|
"can_update_from_versions": {"type": "array"},
|
|
"description": {"type": "string"},
|
|
"operating_system": {"type": "string"},
|
|
"state": {
|
|
"type": "string",
|
|
"enum": list(consts.RELEASE_STATES)
|
|
},
|
|
"networks_metadata": {"type": "array"},
|
|
"attributes_metadata": {"type": "object"},
|
|
"volumes_metadata": {"type": "object"},
|
|
"modes_metadata": {"type": "object"},
|
|
"roles_metadata": {"type": "object"},
|
|
"wizard_metadata": {"type": "object"},
|
|
"roles": {"type": "array"},
|
|
"clusters": {"type": "array"},
|
|
"is_deployable": {"type": "boolean"}
|
|
}
|
|
}
|
|
|
|
@classmethod
|
|
def create(cls, data):
|
|
"""Create Release instance with specified parameters in DB.
|
|
Corresponding roles are created in DB using names specified
|
|
in "roles" field. See :func:`update_roles`
|
|
|
|
:param data: dictionary of key-value pairs as object fields
|
|
:returns: Release instance
|
|
"""
|
|
roles = data.pop("roles", None)
|
|
orch_data = data.pop("orchestrator_data", None)
|
|
new_obj = super(Release, cls).create(data)
|
|
if roles:
|
|
cls.update_roles(new_obj, roles)
|
|
if orch_data:
|
|
orch_data["release_id"] = new_obj.id
|
|
ReleaseOrchestratorData.create(orch_data)
|
|
return new_obj
|
|
|
|
@classmethod
|
|
def update(cls, instance, data):
|
|
"""Update existing Release instance with specified parameters.
|
|
Corresponding roles are updated in DB using names specified
|
|
in "roles" field. See :func:`update_roles`
|
|
|
|
:param instance: Release instance
|
|
:param data: dictionary of key-value pairs as object fields
|
|
:returns: Release instance
|
|
"""
|
|
roles = data.pop("roles", None)
|
|
orch_data = data.pop("orchestrator_data", None)
|
|
super(Release, cls).update(instance, data)
|
|
if roles is not None:
|
|
cls.update_roles(instance, roles)
|
|
if orch_data:
|
|
cls.update_orchestrator_data(instance, orch_data)
|
|
return instance
|
|
|
|
@classmethod
|
|
def update_roles(cls, instance, roles):
|
|
"""Update existing Release instance with specified roles.
|
|
Previous ones are deleted.
|
|
|
|
IMPORTANT NOTE: attempting to remove roles that are already
|
|
assigned to nodes will lead to an Exception.
|
|
|
|
:param instance: Release instance
|
|
:param roles: list of new roles names
|
|
:returns: None
|
|
"""
|
|
db().query(models.Role).filter(
|
|
not_(models.Role.name.in_(roles))
|
|
).filter(
|
|
models.Role.release_id == instance.id
|
|
).delete(synchronize_session='fetch')
|
|
db().refresh(instance)
|
|
|
|
added_roles = instance.roles
|
|
for role in roles:
|
|
if role not in added_roles:
|
|
new_role = models.Role(
|
|
name=role,
|
|
release=instance
|
|
)
|
|
db().add(new_role)
|
|
added_roles.append(role)
|
|
db().flush()
|
|
|
|
@classmethod
|
|
def update_orchestrator_data(cls, instance, orchestrator_data):
|
|
orchestrator_data.pop("id", None)
|
|
orchestrator_data["release_id"] = instance.id
|
|
|
|
ReleaseOrchestratorData.update(
|
|
instance.orchestrator_data, orchestrator_data)
|
|
|
|
@classmethod
|
|
def get_orchestrator_data_dict(cls, instance):
|
|
data = instance.orchestrator_data
|
|
return ReleaseOrchestratorData.serializer.serialize(data)
|
|
|
|
@classmethod
|
|
def is_deployable(cls, instance):
|
|
"""Returns whether a given release deployable or not.
|
|
|
|
:param instance: a Release instance
|
|
:returns: True if a given release is deployable; otherwise - False
|
|
"""
|
|
# in experimental mode we deploy all releases
|
|
if 'experimental' in settings.VERSION['feature_groups']:
|
|
return True
|
|
return instance.is_deployable
|
|
|
|
|
|
class ReleaseCollection(NailgunCollection):
|
|
"""Release collection
|
|
"""
|
|
|
|
#: Single Release object class
|
|
single = Release
|