b974a63660
EXPERIMENTAL Glance v3 API has been removed in favor of standalone API (EXPERIMENTAL Artifacts API of v0.1). This patch introduces a new process entry point to run on a different port (9494 by default), with its own configuration file and a paste config. A controller stub for old /v3 api remains in the glnace.api package for the compatibility with existing paste configuration which may reference it. This stub returns a 301 redirects to glare endpoint if it is present or 410 errors otherwise. To reuse the existing version_negotiation middleware some refactoring has been made. Implements blueprint: move-v3-to-glare Change-Id: I5b7bd4cdcc5f2a40fc4a5f74bcc422fd700c4fb0
338 lines
13 KiB
Python
338 lines
13 KiB
Python
# Copyright (c) 2015 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.
|
|
|
|
import uuid
|
|
|
|
from oslo_db.sqlalchemy import models
|
|
from sqlalchemy import BigInteger
|
|
from sqlalchemy import Boolean
|
|
from sqlalchemy import Column
|
|
from sqlalchemy import DateTime
|
|
from sqlalchemy.ext import declarative
|
|
from sqlalchemy import ForeignKey
|
|
from sqlalchemy import Index
|
|
from sqlalchemy import Integer
|
|
from sqlalchemy import Numeric
|
|
from sqlalchemy.orm import backref
|
|
from sqlalchemy.orm import composite
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy import String
|
|
from sqlalchemy import Text
|
|
|
|
from glance.common import semver_db
|
|
from glance.common import timeutils
|
|
import glance.glare as ga
|
|
|
|
BASE = declarative.declarative_base()
|
|
|
|
|
|
class ArtifactBase(models.ModelBase, models.TimestampMixin):
|
|
"""Base class for Artifact Models."""
|
|
|
|
__table_args__ = {'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}
|
|
__table_initialized__ = False
|
|
__protected_attributes__ = set([
|
|
"created_at", "updated_at"])
|
|
|
|
created_at = Column(DateTime, default=lambda: timeutils.utcnow(),
|
|
nullable=False)
|
|
|
|
updated_at = Column(DateTime, default=lambda: timeutils.utcnow(),
|
|
nullable=False, onupdate=lambda: timeutils.utcnow())
|
|
|
|
def save(self, session=None):
|
|
from glance.db.sqlalchemy import api as db_api
|
|
|
|
super(ArtifactBase, self).save(session or db_api.get_session())
|
|
|
|
def keys(self):
|
|
return self.__dict__.keys()
|
|
|
|
def values(self):
|
|
return self.__dict__.values()
|
|
|
|
def items(self):
|
|
return self.__dict__.items()
|
|
|
|
def to_dict(self):
|
|
d = {}
|
|
for c in self.__table__.columns:
|
|
d[c.name] = self[c.name]
|
|
return d
|
|
|
|
|
|
def _parse_property_type_value(prop, show_text_properties=True):
|
|
columns = [
|
|
'int_value',
|
|
'string_value',
|
|
'bool_value',
|
|
'numeric_value']
|
|
if show_text_properties:
|
|
columns.append('text_value')
|
|
|
|
for prop_type in columns:
|
|
if getattr(prop, prop_type) is not None:
|
|
return prop_type.rpartition('_')[0], getattr(prop, prop_type)
|
|
|
|
return None, None
|
|
|
|
|
|
class Artifact(BASE, ArtifactBase):
|
|
__tablename__ = 'artifacts'
|
|
__table_args__ = (
|
|
Index('ix_artifact_name_and_version', 'name', 'version_prefix',
|
|
'version_suffix'),
|
|
Index('ix_artifact_type', 'type_name', 'type_version_prefix',
|
|
'type_version_suffix'),
|
|
Index('ix_artifact_state', 'state'),
|
|
Index('ix_artifact_owner', 'owner'),
|
|
Index('ix_artifact_visibility', 'visibility'),
|
|
{'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'})
|
|
|
|
__protected_attributes__ = ArtifactBase.__protected_attributes__.union(
|
|
set(['published_at', 'deleted_at']))
|
|
|
|
id = Column(String(36), primary_key=True,
|
|
default=lambda: str(uuid.uuid4()))
|
|
name = Column(String(255), nullable=False)
|
|
type_name = Column(String(255), nullable=False)
|
|
type_version_prefix = Column(BigInteger().with_variant(Integer, "sqlite"),
|
|
nullable=False)
|
|
type_version_suffix = Column(String(255))
|
|
type_version_meta = Column(String(255))
|
|
type_version = composite(semver_db.DBVersion, type_version_prefix,
|
|
type_version_suffix, type_version_meta,
|
|
comparator_factory=semver_db.VersionComparator)
|
|
version_prefix = Column(BigInteger().with_variant(Integer, "sqlite"),
|
|
nullable=False)
|
|
version_suffix = Column(String(255))
|
|
version_meta = Column(String(255))
|
|
version = composite(semver_db.DBVersion, version_prefix,
|
|
version_suffix, version_meta,
|
|
comparator_factory=semver_db.VersionComparator)
|
|
description = Column(Text)
|
|
visibility = Column(String(32), nullable=False)
|
|
state = Column(String(32), nullable=False)
|
|
owner = Column(String(255), nullable=False)
|
|
published_at = Column(DateTime)
|
|
deleted_at = Column(DateTime)
|
|
|
|
def to_dict(self, show_level=ga.Showlevel.BASIC,
|
|
show_text_properties=True):
|
|
d = super(Artifact, self).to_dict()
|
|
|
|
d.pop('type_version_prefix')
|
|
d.pop('type_version_suffix')
|
|
d.pop('type_version_meta')
|
|
d.pop('version_prefix')
|
|
d.pop('version_suffix')
|
|
d.pop('version_meta')
|
|
d['type_version'] = str(self.type_version)
|
|
d['version'] = str(self.version)
|
|
|
|
tags = []
|
|
for tag in self.tags:
|
|
tags.append(tag.value)
|
|
d['tags'] = tags
|
|
|
|
if show_level == ga.Showlevel.NONE:
|
|
return d
|
|
|
|
properties = {}
|
|
|
|
# sort properties
|
|
self.properties.sort(key=lambda elem: (elem.name, elem.position))
|
|
|
|
for prop in self.properties:
|
|
proptype, propvalue = _parse_property_type_value(
|
|
prop, show_text_properties)
|
|
if proptype is None:
|
|
continue
|
|
|
|
if prop.position is not None:
|
|
# make array
|
|
for p in properties.keys():
|
|
if p == prop.name:
|
|
# add value to array
|
|
properties[p]['value'].append(dict(type=proptype,
|
|
value=propvalue))
|
|
break
|
|
else:
|
|
# create new array
|
|
p = dict(type='array',
|
|
value=[])
|
|
p['value'].append(dict(type=proptype,
|
|
value=propvalue))
|
|
properties[prop.name] = p
|
|
else:
|
|
# make scalar
|
|
properties[prop.name] = dict(type=proptype,
|
|
value=propvalue)
|
|
d['properties'] = properties
|
|
|
|
blobs = {}
|
|
# sort blobs
|
|
self.blobs.sort(key=lambda elem: elem.position)
|
|
|
|
for blob in self.blobs:
|
|
locations = []
|
|
# sort locations
|
|
blob.locations.sort(key=lambda elem: elem.position)
|
|
for loc in blob.locations:
|
|
locations.append(dict(value=loc.value,
|
|
status=loc.status))
|
|
if blob.name in blobs:
|
|
blobs[blob.name].append(dict(size=blob.size,
|
|
checksum=blob.checksum,
|
|
locations=locations,
|
|
item_key=blob.item_key))
|
|
else:
|
|
blobs[blob.name] = []
|
|
blobs[blob.name].append(dict(size=blob.size,
|
|
checksum=blob.checksum,
|
|
locations=locations,
|
|
item_key=blob.item_key))
|
|
|
|
d['blobs'] = blobs
|
|
|
|
return d
|
|
|
|
|
|
class ArtifactDependency(BASE, ArtifactBase):
|
|
__tablename__ = 'artifact_dependencies'
|
|
__table_args__ = (Index('ix_artifact_dependencies_source_id',
|
|
'artifact_source'),
|
|
Index('ix_artifact_dependencies_origin_id',
|
|
'artifact_origin'),
|
|
Index('ix_artifact_dependencies_dest_id',
|
|
'artifact_dest'),
|
|
Index('ix_artifact_dependencies_direct_dependencies',
|
|
'artifact_source', 'is_direct'),
|
|
{'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'})
|
|
|
|
id = Column(String(36), primary_key=True, nullable=False,
|
|
default=lambda: str(uuid.uuid4()))
|
|
artifact_source = Column(String(36), ForeignKey('artifacts.id'),
|
|
nullable=False)
|
|
artifact_dest = Column(String(36), ForeignKey('artifacts.id'),
|
|
nullable=False)
|
|
artifact_origin = Column(String(36), ForeignKey('artifacts.id'),
|
|
nullable=False)
|
|
is_direct = Column(Boolean, nullable=False)
|
|
position = Column(Integer)
|
|
name = Column(String(36))
|
|
|
|
source = relationship('Artifact',
|
|
backref=backref('dependencies', cascade="all, "
|
|
"delete"),
|
|
foreign_keys="ArtifactDependency.artifact_source")
|
|
dest = relationship('Artifact',
|
|
foreign_keys="ArtifactDependency.artifact_dest")
|
|
origin = relationship('Artifact',
|
|
foreign_keys="ArtifactDependency.artifact_origin")
|
|
|
|
|
|
class ArtifactTag(BASE, ArtifactBase):
|
|
__tablename__ = 'artifact_tags'
|
|
__table_args__ = (Index('ix_artifact_tags_artifact_id', 'artifact_id'),
|
|
Index('ix_artifact_tags_artifact_id_tag_value',
|
|
'artifact_id', 'value'),
|
|
{'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'},)
|
|
|
|
id = Column(String(36), primary_key=True, nullable=False,
|
|
default=lambda: str(uuid.uuid4()))
|
|
artifact_id = Column(String(36), ForeignKey('artifacts.id'),
|
|
nullable=False)
|
|
artifact = relationship(Artifact,
|
|
backref=backref('tags',
|
|
cascade="all, delete-orphan"))
|
|
value = Column(String(255), nullable=False)
|
|
|
|
|
|
class ArtifactProperty(BASE, ArtifactBase):
|
|
__tablename__ = 'artifact_properties'
|
|
__table_args__ = (
|
|
Index('ix_artifact_properties_artifact_id', 'artifact_id'),
|
|
Index('ix_artifact_properties_name', 'name'),
|
|
{'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'},)
|
|
id = Column(String(36), primary_key=True, nullable=False,
|
|
default=lambda: str(uuid.uuid4()))
|
|
artifact_id = Column(String(36), ForeignKey('artifacts.id'),
|
|
nullable=False)
|
|
artifact = relationship(Artifact,
|
|
backref=backref('properties',
|
|
cascade="all, delete-orphan"))
|
|
name = Column(String(255), nullable=False)
|
|
string_value = Column(String(255))
|
|
int_value = Column(Integer)
|
|
numeric_value = Column(Numeric)
|
|
bool_value = Column(Boolean)
|
|
text_value = Column(Text)
|
|
position = Column(Integer)
|
|
|
|
|
|
class ArtifactBlob(BASE, ArtifactBase):
|
|
__tablename__ = 'artifact_blobs'
|
|
__table_args__ = (
|
|
Index('ix_artifact_blobs_artifact_id', 'artifact_id'),
|
|
Index('ix_artifact_blobs_name', 'name'),
|
|
{'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'},)
|
|
id = Column(String(36), primary_key=True, nullable=False,
|
|
default=lambda: str(uuid.uuid4()))
|
|
artifact_id = Column(String(36), ForeignKey('artifacts.id'),
|
|
nullable=False)
|
|
name = Column(String(255), nullable=False)
|
|
item_key = Column(String(329))
|
|
size = Column(BigInteger().with_variant(Integer, "sqlite"),
|
|
nullable=False)
|
|
checksum = Column(String(32))
|
|
position = Column(Integer)
|
|
artifact = relationship(Artifact,
|
|
backref=backref('blobs',
|
|
cascade="all, delete-orphan"))
|
|
|
|
|
|
class ArtifactBlobLocation(BASE, ArtifactBase):
|
|
__tablename__ = 'artifact_blob_locations'
|
|
__table_args__ = (Index('ix_artifact_blob_locations_blob_id',
|
|
'blob_id'),
|
|
{'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'})
|
|
|
|
id = Column(String(36), primary_key=True, nullable=False,
|
|
default=lambda: str(uuid.uuid4()))
|
|
blob_id = Column(String(36), ForeignKey('artifact_blobs.id'),
|
|
nullable=False)
|
|
value = Column(Text, nullable=False)
|
|
position = Column(Integer)
|
|
status = Column(String(36), default='active', nullable=True)
|
|
blob = relationship(ArtifactBlob,
|
|
backref=backref('locations',
|
|
cascade="all, delete-orphan"))
|
|
|
|
|
|
def register_models(engine):
|
|
"""Create database tables for all models with the given engine."""
|
|
models = (Artifact, ArtifactTag, ArtifactProperty,
|
|
ArtifactBlob, ArtifactBlobLocation, ArtifactDependency)
|
|
for model in models:
|
|
model.metadata.create_all(engine)
|
|
|
|
|
|
def unregister_models(engine):
|
|
"""Drop database tables for all models with the given engine."""
|
|
models = (ArtifactDependency, ArtifactBlobLocation, ArtifactBlob,
|
|
ArtifactProperty, ArtifactTag, Artifact)
|
|
for model in models:
|
|
model.metadata.drop_all(engine)
|