283 lines
9.2 KiB
Python
283 lines
9.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.
|
|
|
|
from random import choice
|
|
import string
|
|
|
|
from sqlalchemy import Boolean
|
|
from sqlalchemy import Column
|
|
from sqlalchemy import Enum
|
|
from sqlalchemy import ForeignKey
|
|
from sqlalchemy import Integer
|
|
from sqlalchemy import Unicode
|
|
from sqlalchemy.orm import relationship, backref
|
|
|
|
from nailgun import consts
|
|
|
|
from nailgun.db import db
|
|
from nailgun.db.sqlalchemy.models.base import Base
|
|
from nailgun.db.sqlalchemy.models.fields import JSON
|
|
from nailgun.db.sqlalchemy.models.node import Node
|
|
from nailgun.logger import logger
|
|
from nailgun.settings import settings
|
|
from nailgun.utils import dict_merge
|
|
|
|
|
|
class ClusterChanges(Base):
|
|
__tablename__ = 'cluster_changes'
|
|
POSSIBLE_CHANGES = (
|
|
'networks',
|
|
'attributes',
|
|
'disks'
|
|
)
|
|
id = Column(Integer, primary_key=True)
|
|
cluster_id = Column(Integer, ForeignKey('clusters.id'))
|
|
node_id = Column(Integer, ForeignKey('nodes.id', ondelete='CASCADE'))
|
|
name = Column(
|
|
Enum(*POSSIBLE_CHANGES, name='possible_changes'),
|
|
nullable=False
|
|
)
|
|
|
|
|
|
class Cluster(Base):
|
|
__tablename__ = 'clusters'
|
|
id = Column(Integer, primary_key=True)
|
|
mode = Column(
|
|
Enum(*consts.CLUSTER_MODES, name='cluster_mode'),
|
|
nullable=False,
|
|
default=consts.CLUSTER_MODES.multinode
|
|
)
|
|
status = Column(
|
|
Enum(*consts.CLUSTER_STATUSES, name='cluster_status'),
|
|
nullable=False,
|
|
default=consts.CLUSTER_STATUSES.new
|
|
)
|
|
net_provider = Column(
|
|
Enum(*consts.CLUSTER_NET_PROVIDERS, name='net_provider'),
|
|
nullable=False,
|
|
default=consts.CLUSTER_NET_PROVIDERS.nova_network
|
|
)
|
|
net_l23_provider = Column(
|
|
Enum(*consts.CLUSTER_NET_L23_PROVIDERS, name='net_l23_provider'),
|
|
nullable=False,
|
|
default=consts.CLUSTER_NET_L23_PROVIDERS.ovs
|
|
)
|
|
net_segment_type = Column(
|
|
Enum(*consts.CLUSTER_NET_SEGMENT_TYPES,
|
|
name='net_segment_type'),
|
|
nullable=False,
|
|
default=consts.CLUSTER_NET_SEGMENT_TYPES.vlan
|
|
)
|
|
net_manager = Column(
|
|
Enum(*consts.CLUSTER_NET_MANAGERS, name='cluster_net_manager'),
|
|
nullable=False,
|
|
default=consts.CLUSTER_NET_MANAGERS.FlatDHCPManager
|
|
)
|
|
grouping = Column(
|
|
Enum(*consts.CLUSTER_GROUPING, name='cluster_grouping'),
|
|
nullable=False,
|
|
default=consts.CLUSTER_GROUPING.roles
|
|
)
|
|
name = Column(Unicode(50), unique=True, nullable=False)
|
|
release_id = Column(Integer, ForeignKey('releases.id'), nullable=False)
|
|
nodes = relationship(
|
|
"Node", backref="cluster", cascade="delete", order_by='Node.id')
|
|
tasks = relationship("Task", backref="cluster", cascade="delete")
|
|
attributes = relationship("Attributes", uselist=False,
|
|
backref="cluster", cascade="delete")
|
|
changes_list = relationship("ClusterChanges", backref="cluster",
|
|
cascade="delete")
|
|
# We must keep all notifications even if cluster is removed.
|
|
# It is because we want user to be able to see
|
|
# the notification history so that is why we don't use
|
|
# cascade="delete" in this relationship
|
|
# During cluster deletion sqlalchemy engine will set null
|
|
# into cluster foreign key column of notification entity
|
|
notifications = relationship("Notification", backref="cluster")
|
|
network_groups = relationship(
|
|
"NetworkGroup",
|
|
backref="cluster",
|
|
cascade="delete",
|
|
order_by="NetworkGroup.id"
|
|
)
|
|
dns_nameservers = Column(JSON, default=[
|
|
"8.8.8.8",
|
|
"8.8.4.4"
|
|
])
|
|
replaced_deployment_info = Column(JSON, default={})
|
|
replaced_provisioning_info = Column(JSON, default={})
|
|
is_customized = Column(Boolean, default=False)
|
|
|
|
neutron_config = relationship("NeutronConfig",
|
|
backref=backref("cluster"),
|
|
cascade="all,delete",
|
|
uselist=False)
|
|
|
|
def replace_provisioning_info(self, data):
|
|
self.replaced_provisioning_info = data
|
|
self.is_customized = True
|
|
return self.replaced_provisioning_info
|
|
|
|
def replace_deployment_info(self, data):
|
|
self.replaced_deployment_info = data
|
|
self.is_customized = True
|
|
return self.replaced_deployment_info
|
|
|
|
@property
|
|
def changes(self):
|
|
return [
|
|
{"name": i.name, "node_id": i.node_id}
|
|
for i in self.changes_list
|
|
]
|
|
|
|
@changes.setter
|
|
def changes(self, value):
|
|
self.changes_list = value
|
|
|
|
@property
|
|
def is_ha_mode(self):
|
|
return self.mode in ('ha_full', 'ha_compact')
|
|
|
|
@property
|
|
def full_name(self):
|
|
return '%s (id=%s, mode=%s)' % (self.name, self.id, self.mode)
|
|
|
|
@property
|
|
def is_locked(self):
|
|
if self.status in ("new", "stopped") and not \
|
|
db().query(Node).filter_by(
|
|
cluster_id=self.id,
|
|
status="ready"
|
|
).count():
|
|
return False
|
|
return True
|
|
|
|
def add_pending_changes(self, changes_type, node_id=None):
|
|
ex_chs = db().query(ClusterChanges).filter_by(
|
|
cluster=self,
|
|
name=changes_type
|
|
)
|
|
if not node_id:
|
|
ex_chs = ex_chs.first()
|
|
else:
|
|
ex_chs = ex_chs.filter_by(node_id=node_id).first()
|
|
# do nothing if changes with the same name already pending
|
|
if ex_chs:
|
|
return
|
|
ch = ClusterChanges(
|
|
cluster_id=self.id,
|
|
name=changes_type
|
|
)
|
|
if node_id:
|
|
ch.node_id = node_id
|
|
db().add(ch)
|
|
db().flush()
|
|
|
|
def clear_pending_changes(self, node_id=None):
|
|
chs = db().query(ClusterChanges).filter_by(
|
|
cluster_id=self.id
|
|
)
|
|
if node_id:
|
|
chs = chs.filter_by(node_id=node_id)
|
|
map(db().delete, chs.all())
|
|
db().flush()
|
|
|
|
@property
|
|
def network_manager(self):
|
|
if self.net_provider == 'neutron':
|
|
from nailgun.network.neutron import NeutronManager
|
|
return NeutronManager
|
|
else:
|
|
from nailgun.network.nova_network import NovaNetworkManager
|
|
return NovaNetworkManager
|
|
|
|
|
|
class AttributesGenerators(object):
|
|
@classmethod
|
|
def password(cls, arg=None):
|
|
try:
|
|
length = int(arg)
|
|
except Exception:
|
|
length = 8
|
|
chars = string.letters + string.digits
|
|
return u''.join([choice(chars) for _ in xrange(length)])
|
|
|
|
@classmethod
|
|
def ip(cls, arg=None):
|
|
if str(arg) in ("admin", "master"):
|
|
return settings.MASTER_IP
|
|
return "127.0.0.1"
|
|
|
|
@classmethod
|
|
def identical(cls, arg=None):
|
|
return str(arg)
|
|
|
|
|
|
class Attributes(Base):
|
|
__tablename__ = 'attributes'
|
|
id = Column(Integer, primary_key=True)
|
|
cluster_id = Column(Integer, ForeignKey('clusters.id'))
|
|
editable = Column(JSON)
|
|
generated = Column(JSON)
|
|
|
|
def generate_fields(self):
|
|
self.generated = self.traverse(self.generated)
|
|
db().add(self)
|
|
db().flush()
|
|
|
|
@classmethod
|
|
def traverse(cls, cdict):
|
|
new_dict = {}
|
|
if cdict:
|
|
for i, val in cdict.iteritems():
|
|
if isinstance(val, (str, unicode, int, float)):
|
|
new_dict[i] = val
|
|
elif isinstance(val, dict) and "generator" in val:
|
|
try:
|
|
generator = getattr(
|
|
AttributesGenerators,
|
|
val["generator"]
|
|
)
|
|
except AttributeError:
|
|
logger.error("Attribute error: %s" % val["generator"])
|
|
raise
|
|
else:
|
|
new_dict[i] = generator(val.get("generator_arg"))
|
|
else:
|
|
new_dict[i] = cls.traverse(val)
|
|
return new_dict
|
|
|
|
def merged_attrs(self):
|
|
return dict_merge(self.generated, self.editable)
|
|
|
|
def merged_attrs_values(self):
|
|
attrs = self.merged_attrs()
|
|
for group_attrs in attrs.itervalues():
|
|
for attr, value in group_attrs.iteritems():
|
|
if isinstance(value, dict) and 'value' in value:
|
|
group_attrs[attr] = value['value']
|
|
if 'common' in attrs:
|
|
attrs.update(attrs.pop('common'))
|
|
if 'additional_components' in attrs:
|
|
for comp, enabled in attrs['additional_components'].iteritems():
|
|
if isinstance(enabled, bool):
|
|
attrs.setdefault(comp, {}).update({
|
|
"enabled": enabled
|
|
})
|
|
|
|
attrs.pop('additional_components')
|
|
return attrs
|