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

326 lines
10 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2014 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 distutils.version import LooseVersion
from itertools import groupby
import operator
import six
from nailgun.db import db
from nailgun.db.sqlalchemy import models
from nailgun.objects import DeploymentGraph
from nailgun.objects import NailgunCollection
from nailgun.objects import NailgunObject
from nailgun.objects.serializers.plugin import PluginSerializer
from nailgun.plugins.adapters import wrap_plugin
class Plugin(NailgunObject):
model = models.Plugin
serializer = PluginSerializer
@classmethod
def create(cls, data):
# accidental because i've seen this way of tasks creation only in tests
deployment_tasks = data.pop('deployment_tasks', [])
new_plugin = super(Plugin, cls).create(data)
# create default graph in any case
DeploymentGraph.create_for_model(
{'tasks': deployment_tasks}, new_plugin)
plugin_adapter = wrap_plugin(new_plugin)
cls.update(new_plugin, plugin_adapter.get_metadata())
ClusterPlugin.add_compatible_clusters(new_plugin)
return new_plugin
@classmethod
def get_by_name_version(cls, name, version):
return db()\
.query(cls.model)\
.filter_by(name=name, version=version)\
.first()
@classmethod
def delete(cls, instance):
"""Delete plugin.
:param instance: Plugin model instance
:type instance: models.Plugin
"""
DeploymentGraph.delete_for_parent(instance)
super(Plugin, cls).delete(instance)
class PluginCollection(NailgunCollection):
single = Plugin
@classmethod
def all_newest(cls):
"""Returns plugins in most recent versions
Example:
There are 4 plugins:
- name: plugin_name, version: 1.0.0
- name: plugin_name, version: 2.0.0
- name: plugin_name, version: 0.1.0
- name: plugin_another_name, version: 1.0.0
In this case the method returns 2 plugins:
- name: plugin_name, version: 2.0.0
- name: plugin_another_name, version: 1.0.0
:returns: list of Plugin models
"""
newest_plugins = []
get_name = operator.attrgetter('name')
grouped_by_name = groupby(sorted(cls.all(), key=get_name), get_name)
for name, plugins in grouped_by_name:
newest_plugin = max(plugins, key=lambda p: LooseVersion(p.version))
newest_plugins.append(newest_plugin)
return newest_plugins
@classmethod
def get_by_uids(cls, plugin_ids):
"""Returns plugins by given IDs.
:param plugin_ids: list of plugin IDs
:type plugin_ids: list
:returns: iterable (SQLAlchemy query)
"""
return cls.filter_by_id_list(cls.all(), plugin_ids)
@classmethod
def get_by_release(cls, release):
"""Returns plugins by given release
:param release: Release instance
:type release: Release DB model
:returns: list -- list of sorted plugins
"""
release_plugins = set()
release_os = release.operating_system.lower()
release_version = release.version
for plugin in PluginCollection.all():
for plugin_release in plugin.releases:
if (release_os == plugin_release.get('os') and
release_version == plugin_release.get('version')):
release_plugins.add(plugin)
return sorted(release_plugins, key=lambda plugin: plugin.name)
class ClusterPlugin(NailgunObject):
model = models.ClusterPlugin
@classmethod
def is_compatible(cls, cluster, plugin):
"""Validates if plugin is compatible with cluster.
:param cluster: A cluster instance
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:param plugin: A plugin instance
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
:return: True if compatible, False if not
:rtype: bool
"""
plugin_adapter = wrap_plugin(plugin)
return plugin_adapter.validate_compatibility(cluster)
@classmethod
def get_compatible_plugins(cls, cluster):
"""Returns a list of plugins that are compatible with a given cluster.
:param cluster: A cluster instance
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:return: A list of plugin instances
:rtype: list
"""
return list(six.moves.filter(
lambda p: cls.is_compatible(cluster, p),
PluginCollection.all()))
@classmethod
def add_compatible_plugins(cls, cluster):
"""Populates 'cluster_plugins' table with compatible plugins.
:param cluster: A cluster instance
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
"""
for plugin in cls.get_compatible_plugins(cluster):
plugin_attributes = dict(plugin.attributes_metadata)
plugin_attributes.pop('metadata', None)
cls.create({
'cluster_id': cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin_attributes
})
@classmethod
def get_compatible_clusters(cls, plugin):
"""Returns a list of clusters that are compatible with a given plugin.
:param plugin: A plugin instance
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
:return: A list of cluster instances
:rtype: list
"""
return list(six.moves.filter(
lambda c: cls.is_compatible(c, plugin),
db().query(models.Cluster)))
@classmethod
def add_compatible_clusters(cls, plugin):
"""Populates 'cluster_plugins' table with compatible cluster.
:param plugin: A plugin instance
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
"""
plugin_attributes = dict(plugin.attributes_metadata)
plugin_attributes.pop('metadata', None)
for cluster in cls.get_compatible_clusters(plugin):
cls.create({
'cluster_id': cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin_attributes
})
@classmethod
def set_attributes(cls, cluster_id, plugin_id, enabled=None, attrs=None):
"""Sets plugin's attributes in cluster_plugins table.
:param cluster_id: Cluster ID
:type cluster_id: int
:param plugin_id: Plugin ID
:type plugin_id: int
:param enabled: Enabled or disabled plugin for given cluster
:type enabled: bool
:param attrs: Plugin metadata
:type attrs: dict
"""
params = {}
if enabled is not None:
params['enabled'] = enabled
if attrs is not None:
params['attributes'] = attrs
db().query(cls.model)\
.filter_by(plugin_id=plugin_id, cluster_id=cluster_id)\
.update(params, synchronize_session='fetch')
db().flush()
@classmethod
def get_connected_plugins_data(cls, cluster_id):
"""Returns plugins and cluster_plugins data connected with cluster.
:param cluster_id: Cluster ID
:type cluster_id: int
:returns: List of mixed data from plugins and cluster_plugins
:rtype: iterable (SQLAlchemy query)
"""
return db().query(
models.Plugin.id,
models.Plugin.name,
models.Plugin.title,
models.Plugin.version,
models.Plugin.is_hotpluggable,
models.Plugin.attributes_metadata,
cls.model.enabled,
cls.model.attributes
).join(cls.model)\
.filter(cls.model.cluster_id == cluster_id)\
.order_by(models.Plugin.name, models.Plugin.version)
@classmethod
def get_connected_plugins(cls, cluster, plugin_ids=None):
"""Returns plugins connected with given cluster.
:param cluster: Cluster instance
:type cluster: Cluster SQLAlchemy model
:param plugin_ids: List of specific plugins ids to chose from
:type plugin_ids: list
:returns: List of plugins
:rtype: iterable (SQLAlchemy query)
"""
plugins = db().query(
models.Plugin
).join(cls.model)\
.filter(cls.model.cluster_id == cluster.id)\
.order_by(models.Plugin.name, models.Plugin.version)
if plugin_ids:
plugins = plugins.filter(cls.model.plugin_id.in_(plugin_ids))
return plugins
@classmethod
def get_connected_clusters(cls, plugin_id):
"""Returns clusters connected with given plugin.
:param plugin_id: Plugin ID
:type plugin_id: int
:returns: List of clusters
:rtype: iterable (SQLAlchemy query)
"""
return db()\
.query(models.Cluster)\
.join(cls.model)\
.filter(cls.model.plugin_id == plugin_id)\
.order_by(models.Cluster.name)
@classmethod
def get_enabled(cls, cluster_id):
"""Returns a list of plugins enabled for a given cluster.
:param cluster_id: Cluster ID
:type cluster_id: int
:returns: List of plugin instances
:rtype: iterable (SQLAlchemy query)
"""
return db().query(models.Plugin)\
.join(cls.model)\
.filter(cls.model.cluster_id == cluster_id)\
.filter(cls.model.enabled.is_(True))\
.order_by(models.Plugin.id)
@classmethod
def is_plugin_used(cls, plugin_id):
"""Check if plugin is used for any cluster or not.
:param plugin_id: Plugin ID
:type plugin_id: int
:return: True if some cluster uses this plugin
:rtype: bool
"""
q = db().query(cls.model)\
.filter(cls.model.plugin_id == plugin_id)\
.filter(cls.model.enabled.is_(True))
return db().query(q.exists()).scalar()