fuel-web/nailgun/nailgun/objects/release.py

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