1# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
2# Copyright 2010 United States Government as represented by the
3# Administrator of the National Aeronautics and Space Administration.
4# Copyright 2011 Piston Cloud Computing, Inc.
5# All Rights Reserved.
6#
7#    Licensed under the Apache License, Version 2.0 (the "License"); you may
8#    not use this file except in compliance with the License. You may obtain
9#    a copy of the License at
10#
11#         http://www.apache.org/licenses/LICENSE-2.0
12#
13#    Unless required by applicable law or agreed to in writing, software
14#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16#    License for the specific language governing permissions and limitations
17#    under the License.
18
19"""
20SQLAlchemy models for cinder data.
21"""
22
23from oslo_config import cfg
24from oslo_db.sqlalchemy import models
25from oslo_utils import timeutils
26from sqlalchemy import and_, func, select
27from sqlalchemy import bindparam
28from sqlalchemy import Column, Integer, String, Text, schema, Index
29from sqlalchemy.ext.declarative import declarative_base
30from sqlalchemy import ForeignKey, DateTime, Boolean, UniqueConstraint
31from sqlalchemy.orm import backref, column_property, relationship, validates
32
33
34CONF = cfg.CONF
35BASE = declarative_base()
36
37
38class CinderBase(models.TimestampMixin,
39                 models.ModelBase):
40    """Base class for Cinder Models."""
41
42    __table_args__ = {'mysql_engine': 'InnoDB'}
43
44    # TODO(rpodolyaka): reuse models.SoftDeleteMixin in the next stage
45    #                   of implementing of BP db-cleanup
46    deleted_at = Column(DateTime)
47    deleted = Column(Boolean, default=False)
48    metadata = None
49
50    @staticmethod
51    def delete_values():
52        return {'deleted': True,
53                'deleted_at': timeutils.utcnow()}
54
55    def delete(self, session):
56        """Delete this object."""
57        updated_values = self.delete_values()
58        self.update(updated_values)
59        self.save(session=session)
60        return updated_values
61
62
63class Service(BASE, CinderBase):
64    """Represents a running service on a host."""
65
66    __tablename__ = 'services'
67    id = Column(Integer, primary_key=True)
68    uuid = Column(String(36), nullable=True, index=True)
69    cluster_name = Column(String(255), nullable=True)
70    host = Column(String(255))  # , ForeignKey('hosts.id'))
71    binary = Column(String(255))
72    # We want to overwrite default updated_at definition so we timestamp at
73    # creation as well, so we only need to check updated_at for the heartbeat
74    updated_at = Column(DateTime, default=timeutils.utcnow,
75                        onupdate=timeutils.utcnow)
76    topic = Column(String(255))
77    report_count = Column(Integer, nullable=False, default=0)
78    disabled = Column(Boolean, default=False)
79    availability_zone = Column(String(255), default='cinder')
80    disabled_reason = Column(String(255))
81    # adding column modified_at to contain timestamp
82    # for manual enable/disable of cinder services
83    # updated_at column will now contain timestamps for
84    # periodic updates
85    modified_at = Column(DateTime)
86
87    # Version columns to support rolling upgrade. These report the max RPC API
88    # and objects versions that the manager of the service is able to support.
89    rpc_current_version = Column(String(36))
90    object_current_version = Column(String(36))
91
92    # replication_status can be: enabled, disabled, not-capable, error,
93    # failed-over or not-configured
94    replication_status = Column(String(36), default="not-capable")
95    active_backend_id = Column(String(255))
96    frozen = Column(Boolean, nullable=False, default=False)
97
98    cluster = relationship('Cluster',
99                           backref='services',
100                           foreign_keys=cluster_name,
101                           primaryjoin='and_('
102                                       'Service.cluster_name == Cluster.name,'
103                                       'Service.deleted == False)')
104
105
106class Cluster(BASE, CinderBase):
107    """Represents a cluster of hosts."""
108    __tablename__ = 'clusters'
109    # To remove potential races on creation we have a constraint set on name
110    # and race_preventer fields, and we set value on creation to 0, so 2
111    # clusters with the same name will fail this constraint.  On deletion we
112    # change this field to the same value as the id which will be unique and
113    # will not conflict with the creation of another cluster with the same
114    # name.
115    __table_args__ = (UniqueConstraint('name', 'binary', 'race_preventer'),
116                      CinderBase.__table_args__)
117
118    id = Column(Integer, primary_key=True)
119    # NOTE(geguileo): Name is constructed in the same way that Server.host but
120    # using cluster configuration option instead of host.
121    name = Column(String(255), nullable=False)
122    binary = Column(String(255), nullable=False)
123    disabled = Column(Boolean, default=False)
124    disabled_reason = Column(String(255))
125    race_preventer = Column(Integer, nullable=False, default=0)
126
127    replication_status = Column(String(36), default="not-capable")
128    active_backend_id = Column(String(255))
129    frozen = Column(Boolean, nullable=False, default=False)
130
131    # Last heartbeat reported by any of the services of this cluster.  This is
132    # not deferred since we always want to load this field.
133    last_heartbeat = column_property(
134        select([func.max(Service.updated_at)]).
135        where(and_(Service.cluster_name == name, ~Service.deleted)).
136        correlate_except(Service), deferred=False)
137
138    # Number of existing services for this cluster
139    num_hosts = column_property(
140        select([func.count(Service.id)]).
141        where(and_(Service.cluster_name == name, ~Service.deleted)).
142        correlate_except(Service),
143        group='services_summary', deferred=True)
144
145    # Number of services that are down for this cluster
146    num_down_hosts = column_property(
147        select([func.count(Service.id)]).
148        where(and_(Service.cluster_name == name,
149                   ~Service.deleted,
150                   Service.updated_at < bindparam('expired'))).
151        correlate_except(Service),
152        group='services_summary', deferred=True)
153
154    @staticmethod
155    def delete_values():
156        return {'race_preventer': Cluster.id,
157                'deleted': True,
158                'deleted_at': timeutils.utcnow()}
159
160
161class ConsistencyGroup(BASE, CinderBase):
162    """Represents a consistencygroup."""
163    __tablename__ = 'consistencygroups'
164    id = Column(String(36), primary_key=True)
165
166    user_id = Column(String(255), nullable=False)
167    project_id = Column(String(255), nullable=False)
168
169    cluster_name = Column(String(255), nullable=True)
170    host = Column(String(255))
171    availability_zone = Column(String(255))
172    name = Column(String(255))
173    description = Column(String(255))
174    volume_type_id = Column(String(255))
175    status = Column(String(255))
176    cgsnapshot_id = Column(String(36))
177    source_cgid = Column(String(36))
178
179
180class Group(BASE, CinderBase):
181    """Represents a generic volume group."""
182    __tablename__ = 'groups'
183    id = Column(String(36), primary_key=True)
184
185    user_id = Column(String(255), nullable=False)
186    project_id = Column(String(255), nullable=False)
187
188    cluster_name = Column(String(255))
189    host = Column(String(255))
190    availability_zone = Column(String(255))
191    name = Column(String(255))
192    description = Column(String(255))
193    status = Column(String(255))
194    group_type_id = Column(String(36))
195    group_snapshot_id = Column(String(36))
196    source_group_id = Column(String(36))
197
198    replication_status = Column(String(255))
199
200
201class Cgsnapshot(BASE, CinderBase):
202    """Represents a cgsnapshot."""
203    __tablename__ = 'cgsnapshots'
204    id = Column(String(36), primary_key=True)
205
206    consistencygroup_id = Column(String(36), index=True)
207    user_id = Column(String(255), nullable=False)
208    project_id = Column(String(255), nullable=False)
209
210    name = Column(String(255))
211    description = Column(String(255))
212    status = Column(String(255))
213
214    consistencygroup = relationship(
215        ConsistencyGroup,
216        backref="cgsnapshots",
217        foreign_keys=consistencygroup_id,
218        primaryjoin='Cgsnapshot.consistencygroup_id == ConsistencyGroup.id')
219
220
221class GroupSnapshot(BASE, CinderBase):
222    """Represents a group snapshot."""
223    __tablename__ = 'group_snapshots'
224    id = Column(String(36), primary_key=True)
225
226    group_id = Column(String(36), nullable=False, index=True)
227    user_id = Column(String(255))
228    project_id = Column(String(255))
229
230    name = Column(String(255))
231    description = Column(String(255))
232    status = Column(String(255))
233    group_type_id = Column(String(36))
234
235    group = relationship(
236        Group,
237        backref="group_snapshots",
238        foreign_keys=group_id,
239        primaryjoin='GroupSnapshot.group_id == Group.id')
240
241
242class Volume(BASE, CinderBase):
243    """Represents a block storage device that can be attached to a vm."""
244    __tablename__ = 'volumes'
245    __table_args__ = (Index('volumes_service_uuid_idx',
246                            'deleted', 'service_uuid'),
247                      CinderBase.__table_args__)
248
249    id = Column(String(36), primary_key=True)
250    _name_id = Column(String(36))  # Don't access/modify this directly!
251
252    @property
253    def name_id(self):
254        return self.id if not self._name_id else self._name_id
255
256    @name_id.setter
257    def name_id(self, value):
258        self._name_id = value
259
260    @property
261    def name(self):
262        return CONF.volume_name_template % self.name_id
263
264    ec2_id = Column(Integer)
265    user_id = Column(String(255))
266    project_id = Column(String(255))
267
268    snapshot_id = Column(String(36))
269
270    cluster_name = Column(String(255), nullable=True)
271    host = Column(String(255))  # , ForeignKey('hosts.id'))
272    size = Column(Integer)
273    availability_zone = Column(String(255))  # TODO(vish): foreign key?
274    status = Column(String(255))  # TODO(vish): enum?
275    attach_status = Column(String(255))  # TODO(vish): enum
276    migration_status = Column(String(255))
277
278    scheduled_at = Column(DateTime)
279    launched_at = Column(DateTime)
280    terminated_at = Column(DateTime)
281
282    display_name = Column(String(255))
283    display_description = Column(String(255))
284
285    provider_location = Column(String(255))
286    provider_auth = Column(String(255))
287    provider_geometry = Column(String(255))
288    provider_id = Column(String(255))
289
290    volume_type_id = Column(String(36))
291    source_volid = Column(String(36))
292    encryption_key_id = Column(String(36))
293
294    consistencygroup_id = Column(String(36), index=True)
295    group_id = Column(String(36), index=True)
296
297    bootable = Column(Boolean, default=False)
298    multiattach = Column(Boolean, default=False)
299
300    replication_status = Column(String(255))
301    replication_extended_status = Column(String(255))
302    replication_driver_data = Column(String(255))
303
304    previous_status = Column(String(255))
305
306    consistencygroup = relationship(
307        ConsistencyGroup,
308        backref="volumes",
309        foreign_keys=consistencygroup_id,
310        primaryjoin='Volume.consistencygroup_id == ConsistencyGroup.id')
311
312    group = relationship(
313        Group,
314        backref="volumes",
315        foreign_keys=group_id,
316        primaryjoin='Volume.group_id == Group.id')
317
318    service_uuid = Column(String(36), index=True)
319    service = relationship(Service,
320                           backref="volumes",
321                           foreign_keys=service_uuid,
322                           primaryjoin='Volume.service_uuid == Service.uuid')
323    shared_targets = Column(Boolean, default=True)  # make an FK of service?
324
325
326class VolumeMetadata(BASE, CinderBase):
327    """Represents a metadata key/value pair for a volume."""
328    __tablename__ = 'volume_metadata'
329    id = Column(Integer, primary_key=True)
330    key = Column(String(255))
331    value = Column(String(255))
332    volume_id = Column(String(36), ForeignKey('volumes.id'), nullable=False,
333                       index=True)
334    volume = relationship(Volume, backref="volume_metadata",
335                          foreign_keys=volume_id,
336                          primaryjoin='and_('
337                          'VolumeMetadata.volume_id == Volume.id,'
338                          'VolumeMetadata.deleted == False)')
339
340
341class VolumeAdminMetadata(BASE, CinderBase):
342    """Represents an administrator metadata key/value pair for a volume."""
343    __tablename__ = 'volume_admin_metadata'
344    id = Column(Integer, primary_key=True)
345    key = Column(String(255))
346    value = Column(String(255))
347    volume_id = Column(String(36), ForeignKey('volumes.id'), nullable=False,
348                       index=True)
349    volume = relationship(Volume, backref="volume_admin_metadata",
350                          foreign_keys=volume_id,
351                          primaryjoin='and_('
352                          'VolumeAdminMetadata.volume_id == Volume.id,'
353                          'VolumeAdminMetadata.deleted == False)')
354
355
356class VolumeAttachment(BASE, CinderBase):
357    """Represents a volume attachment for a vm."""
358    __tablename__ = 'volume_attachment'
359    id = Column(String(36), primary_key=True)
360
361    volume_id = Column(String(36), ForeignKey('volumes.id'), nullable=False,
362                       index=True)
363    volume = relationship(Volume, backref="volume_attachment",
364                          foreign_keys=volume_id,
365                          primaryjoin='and_('
366                          'VolumeAttachment.volume_id == Volume.id,'
367                          'VolumeAttachment.deleted == False)')
368    instance_uuid = Column(String(36))
369    attached_host = Column(String(255))
370    mountpoint = Column(String(255))
371    attach_time = Column(DateTime)
372    detach_time = Column(DateTime)
373    attach_status = Column(String(255))
374    attach_mode = Column(String(255))
375    connection_info = Column(Text)
376    # Stores a serialized json dict of host connector information from brick.
377    connector = Column(Text)
378
379
380class VolumeTypes(BASE, CinderBase):
381    """Represent possible volume_types of volumes offered."""
382    __tablename__ = "volume_types"
383    id = Column(String(36), primary_key=True)
384    name = Column(String(255))
385    description = Column(String(255))
386    # A reference to qos_specs entity
387    qos_specs_id = Column(String(36),
388                          ForeignKey('quality_of_service_specs.id'),
389                          index=True)
390    is_public = Column(Boolean, default=True)
391    volumes = relationship(Volume,
392                           backref=backref('volume_type', uselist=False),
393                           foreign_keys=id,
394                           primaryjoin='and_('
395                           'Volume.volume_type_id == VolumeTypes.id, '
396                           'VolumeTypes.deleted == False)')
397
398
399class GroupTypes(BASE, CinderBase):
400    """Represent possible group_types of groups offered."""
401    __tablename__ = "group_types"
402    id = Column(String(36), primary_key=True)
403    name = Column(String(255))
404    description = Column(String(255))
405    is_public = Column(Boolean, default=True)
406    groups = relationship(Group,
407                          backref=backref('group_type', uselist=False),
408                          foreign_keys=id,
409                          primaryjoin='and_('
410                          'Group.group_type_id == GroupTypes.id, '
411                          'GroupTypes.deleted == False)')
412
413
414class GroupVolumeTypeMapping(BASE, CinderBase):
415    """Represent mapping between groups and volume_types."""
416    __tablename__ = "group_volume_type_mapping"
417    id = Column(Integer, primary_key=True, nullable=False)
418    volume_type_id = Column(String(36),
419                            ForeignKey('volume_types.id'),
420                            nullable=False, index=True)
421    group_id = Column(String(36),
422                      ForeignKey('groups.id'),
423                      nullable=False, index=True)
424
425    group = relationship(
426        Group,
427        backref="volume_types",
428        foreign_keys=group_id,
429        primaryjoin='and_('
430        'GroupVolumeTypeMapping.group_id == Group.id,'
431        'GroupVolumeTypeMapping.deleted == False)'
432    )
433
434
435class VolumeTypeProjects(BASE, CinderBase):
436    """Represent projects associated volume_types."""
437    __tablename__ = "volume_type_projects"
438    __table_args__ = (schema.UniqueConstraint(
439        "volume_type_id", "project_id", "deleted",
440        name="uniq_volume_type_projects0volume_type_id0project_id0deleted"),
441        CinderBase.__table_args__)
442    id = Column(Integer, primary_key=True)
443    volume_type_id = Column(String, ForeignKey('volume_types.id'),
444                            nullable=False)
445    project_id = Column(String(255))
446    deleted = Column(Integer, default=0)
447
448    volume_type = relationship(
449        VolumeTypes,
450        backref="projects",
451        foreign_keys=volume_type_id,
452        primaryjoin='and_('
453        'VolumeTypeProjects.volume_type_id == VolumeTypes.id,'
454        'VolumeTypeProjects.deleted == 0)')
455
456
457class GroupTypeProjects(BASE, CinderBase):
458    """Represent projects associated group_types."""
459    __tablename__ = "group_type_projects"
460    __table_args__ = (schema.UniqueConstraint(
461        "group_type_id", "project_id", "deleted",
462        name="uniq_group_type_projects0group_type_id0project_id0deleted"),
463        CinderBase.__table_args__)
464    id = Column(Integer, primary_key=True)
465    group_type_id = Column(String, ForeignKey('group_types.id'),
466                           nullable=False)
467    project_id = Column(String(255))
468
469    group_type = relationship(
470        GroupTypes,
471        backref="projects",
472        foreign_keys=group_type_id,
473        primaryjoin='and_('
474        'GroupTypeProjects.group_type_id == GroupTypes.id,'
475        'GroupTypeProjects.deleted == False)')
476
477
478class VolumeTypeExtraSpecs(BASE, CinderBase):
479    """Represents additional specs as key/value pairs for a volume_type."""
480    __tablename__ = 'volume_type_extra_specs'
481    id = Column(Integer, primary_key=True)
482    key = Column(String(255))
483    value = Column(String(255))
484    volume_type_id = Column(String(36),
485                            ForeignKey('volume_types.id'),
486                            nullable=False, index=True)
487    volume_type = relationship(
488        VolumeTypes,
489        backref="extra_specs",
490        foreign_keys=volume_type_id,
491        primaryjoin='and_('
492        'VolumeTypeExtraSpecs.volume_type_id == VolumeTypes.id,'
493        'VolumeTypeExtraSpecs.deleted == False)'
494    )
495
496
497class GroupTypeSpecs(BASE, CinderBase):
498    """Represents additional specs as key/value pairs for a group_type."""
499    __tablename__ = 'group_type_specs'
500    id = Column(Integer, primary_key=True)
501    key = Column(String(255))
502    value = Column(String(255))
503    group_type_id = Column(String(36),
504                           ForeignKey('group_types.id'),
505                           nullable=False, index=True)
506    group_type = relationship(
507        GroupTypes,
508        backref="group_specs",
509        foreign_keys=group_type_id,
510        primaryjoin='and_('
511        'GroupTypeSpecs.group_type_id == GroupTypes.id,'
512        'GroupTypeSpecs.deleted == False)'
513    )
514
515
516class QualityOfServiceSpecs(BASE, CinderBase):
517    """Represents QoS specs as key/value pairs.
518
519    QoS specs is standalone entity that can be associated/disassociated
520    with volume types (one to many relation).  Adjacency list relationship
521    pattern is used in this model in order to represent following hierarchical
522    data with in flat table, e.g, following structure:
523
524    .. code-block:: none
525
526      qos-specs-1  'Rate-Limit'
527           |
528           +------>  consumer = 'front-end'
529           +------>  total_bytes_sec = 1048576
530           +------>  total_iops_sec = 500
531
532      qos-specs-2  'QoS_Level1'
533           |
534           +------>  consumer = 'back-end'
535           +------>  max-iops =  1000
536           +------>  min-iops = 200
537
538      is represented by:
539
540        id       specs_id       key                  value
541      ------     --------   -------------            -----
542      UUID-1     NULL       QoSSpec_Name           Rate-Limit
543      UUID-2     UUID-1       consumer             front-end
544      UUID-3     UUID-1     total_bytes_sec        1048576
545      UUID-4     UUID-1     total_iops_sec           500
546      UUID-5     NULL       QoSSpec_Name           QoS_Level1
547      UUID-6     UUID-5       consumer             back-end
548      UUID-7     UUID-5       max-iops               1000
549      UUID-8     UUID-5       min-iops               200
550    """
551    __tablename__ = 'quality_of_service_specs'
552    id = Column(String(36), primary_key=True)
553    specs_id = Column(String(36), ForeignKey(id), index=True)
554    key = Column(String(255))
555    value = Column(String(255))
556
557    specs = relationship(
558        "QualityOfServiceSpecs",
559        cascade="all, delete-orphan",
560        backref=backref("qos_spec", remote_side=id),
561    )
562
563    vol_types = relationship(
564        VolumeTypes,
565        backref=backref('qos_specs'),
566        foreign_keys=id,
567        primaryjoin='and_('
568                    'or_(VolumeTypes.qos_specs_id == '
569                    'QualityOfServiceSpecs.id,'
570                    'VolumeTypes.qos_specs_id == '
571                    'QualityOfServiceSpecs.specs_id),'
572                    'QualityOfServiceSpecs.deleted == False)')
573
574
575class VolumeGlanceMetadata(BASE, CinderBase):
576    """Glance metadata for a bootable volume."""
577    __tablename__ = 'volume_glance_metadata'
578    id = Column(Integer, primary_key=True, nullable=False)
579    volume_id = Column(String(36), ForeignKey('volumes.id'), index=True)
580    snapshot_id = Column(String(36), ForeignKey('snapshots.id'), index=True)
581    key = Column(String(255))
582    value = Column(Text)
583    volume = relationship(Volume, backref="volume_glance_metadata",
584                          foreign_keys=volume_id,
585                          primaryjoin='and_('
586                          'VolumeGlanceMetadata.volume_id == Volume.id,'
587                          'VolumeGlanceMetadata.deleted == False)')
588
589
590class Quota(BASE, CinderBase):
591    """Represents a single quota override for a project.
592
593    If there is no row for a given project id and resource, then the
594    default for the quota class is used.  If there is no row for a
595    given quota class and resource, then the default for the
596    deployment is used. If the row is present but the hard limit is
597    Null, then the resource is unlimited.
598    """
599
600    __tablename__ = 'quotas'
601    id = Column(Integer, primary_key=True)
602
603    project_id = Column(String(255), index=True)
604
605    resource = Column(String(255))
606    hard_limit = Column(Integer, nullable=True)
607    allocated = Column(Integer, default=0)
608
609
610class QuotaClass(BASE, CinderBase):
611    """Represents a single quota override for a quota class.
612
613    If there is no row for a given quota class and resource, then the
614    default for the deployment is used.  If the row is present but the
615    hard limit is Null, then the resource is unlimited.
616    """
617
618    __tablename__ = 'quota_classes'
619    id = Column(Integer, primary_key=True)
620
621    class_name = Column(String(255), index=True)
622
623    resource = Column(String(255))
624    hard_limit = Column(Integer, nullable=True)
625
626
627class QuotaUsage(BASE, CinderBase):
628    """Represents the current usage for a given resource."""
629
630    __tablename__ = 'quota_usages'
631    id = Column(Integer, primary_key=True)
632
633    project_id = Column(String(255), index=True)
634    resource = Column(String(255), index=True)
635
636    in_use = Column(Integer)
637    reserved = Column(Integer)
638
639    @property
640    def total(self):
641        return self.in_use + self.reserved
642
643    until_refresh = Column(Integer, nullable=True)
644
645
646class Reservation(BASE, CinderBase):
647    """Represents a resource reservation for quotas."""
648
649    __tablename__ = 'reservations'
650    __table_args__ = (Index('reservations_deleted_expire_idx',
651                            'deleted', 'expire'),
652                      Index('reservations_deleted_uuid_idx',
653                            'deleted', 'uuid'),
654                      CinderBase.__table_args__)
655
656    id = Column(Integer, primary_key=True)
657    uuid = Column(String(36), nullable=False)
658
659    usage_id = Column(Integer, ForeignKey('quota_usages.id'), nullable=True,
660                      index=True)
661    allocated_id = Column(Integer, ForeignKey('quotas.id'), nullable=True,
662                          index=True)
663
664    project_id = Column(String(255), index=True)
665    resource = Column(String(255))
666
667    delta = Column(Integer)
668    expire = Column(DateTime, nullable=False)
669
670    usage = relationship(
671        "QuotaUsage",
672        foreign_keys=usage_id,
673        primaryjoin='and_(Reservation.usage_id == QuotaUsage.id,'
674                    'QuotaUsage.deleted == 0)')
675    quota = relationship(
676        "Quota",
677        foreign_keys=allocated_id,
678        primaryjoin='and_(Reservation.allocated_id == Quota.id)')
679
680
681class Snapshot(BASE, CinderBase):
682    """Represents a snapshot of volume."""
683    __tablename__ = 'snapshots'
684    id = Column(String(36), primary_key=True)
685
686    @property
687    def name(self):
688        return CONF.snapshot_name_template % self.id
689
690    @property
691    def volume_name(self):
692        return self.volume.name  # pylint: disable=E1101
693
694    user_id = Column(String(255))
695    project_id = Column(String(255))
696
697    volume_id = Column(String(36), index=True)
698    cgsnapshot_id = Column(String(36), index=True)
699    group_snapshot_id = Column(String(36), index=True)
700    status = Column(String(255))
701    progress = Column(String(255))
702    volume_size = Column(Integer)
703
704    display_name = Column(String(255))
705    display_description = Column(String(255))
706
707    encryption_key_id = Column(String(36))
708    volume_type_id = Column(String(36))
709
710    provider_location = Column(String(255))
711    provider_id = Column(String(255))
712    provider_auth = Column(String(255))
713
714    volume = relationship(Volume, backref="snapshots",
715                          foreign_keys=volume_id,
716                          primaryjoin='Snapshot.volume_id == Volume.id')
717
718    cgsnapshot = relationship(
719        Cgsnapshot,
720        backref="snapshots",
721        foreign_keys=cgsnapshot_id,
722        primaryjoin='Snapshot.cgsnapshot_id == Cgsnapshot.id')
723
724    group_snapshot = relationship(
725        GroupSnapshot,
726        backref="snapshots",
727        foreign_keys=group_snapshot_id,
728        primaryjoin='Snapshot.group_snapshot_id == GroupSnapshot.id')
729
730
731class SnapshotMetadata(BASE, CinderBase):
732    """Represents a metadata key/value pair for a snapshot."""
733    __tablename__ = 'snapshot_metadata'
734    id = Column(Integer, primary_key=True)
735    key = Column(String(255))
736    value = Column(String(255))
737    snapshot_id = Column(String(36),
738                         ForeignKey('snapshots.id'),
739                         nullable=False, index=True)
740    snapshot = relationship(Snapshot, backref="snapshot_metadata",
741                            foreign_keys=snapshot_id,
742                            primaryjoin='and_('
743                            'SnapshotMetadata.snapshot_id == Snapshot.id,'
744                            'SnapshotMetadata.deleted == False)')
745
746
747class Backup(BASE, CinderBase):
748    """Represents a backup of a volume to Swift."""
749    __tablename__ = 'backups'
750    id = Column(String(36), primary_key=True)
751
752    @property
753    def name(self):
754        return CONF.backup_name_template % self.id
755
756    user_id = Column(String(255), nullable=False)
757    project_id = Column(String(255), nullable=False)
758
759    volume_id = Column(String(36), nullable=False)
760    host = Column(String(255))
761    availability_zone = Column(String(255))
762    display_name = Column(String(255))
763    display_description = Column(String(255))
764    container = Column(String(255))
765    parent_id = Column(String(36))
766    status = Column(String(255))
767    fail_reason = Column(String(255))
768    service_metadata = Column(String(255))
769    service = Column(String(255))
770    size = Column(Integer)
771    object_count = Column(Integer)
772    temp_volume_id = Column(String(36))
773    temp_snapshot_id = Column(String(36))
774    num_dependent_backups = Column(Integer)
775    snapshot_id = Column(String(36))
776    data_timestamp = Column(DateTime)
777    restore_volume_id = Column(String(36))
778    encryption_key_id = Column(String(36))
779
780    @validates('fail_reason')
781    def validate_fail_reason(self, key, fail_reason):
782        return fail_reason and fail_reason[:255] or ''
783
784
785class BackupMetadata(BASE, CinderBase):
786    """Represents a metadata key/value pair for a backup."""
787    __tablename__ = 'backup_metadata'
788    id = Column(Integer, primary_key=True)
789    key = Column(String(255))
790    value = Column(String(255))
791    backup_id = Column(String(36), ForeignKey('backups.id'), nullable=False,
792                       index=True)
793    backup = relationship(Backup, backref="backup_metadata",
794                          foreign_keys=backup_id,
795                          primaryjoin='and_('
796                          'BackupMetadata.backup_id == Backup.id,'
797                          'BackupMetadata.deleted == False)')
798
799
800class Encryption(BASE, CinderBase):
801    """Represents encryption requirement for a volume type.
802
803    Encryption here is a set of performance characteristics describing
804    cipher, provider, and key_size for a certain volume type.
805    """
806
807    __tablename__ = 'encryption'
808    encryption_id = Column(String(36), primary_key=True)
809    cipher = Column(String(255))
810    key_size = Column(Integer)
811    provider = Column(String(255))
812    control_location = Column(String(255))
813    volume_type_id = Column(String(36), ForeignKey('volume_types.id'))
814    volume_type = relationship(
815        VolumeTypes,
816        backref="encryption",
817        foreign_keys=volume_type_id,
818        primaryjoin='and_('
819        'Encryption.volume_type_id == VolumeTypes.id,'
820        'Encryption.deleted == False)'
821    )
822
823
824class Transfer(BASE, CinderBase):
825    """Represents a volume transfer request."""
826    __tablename__ = 'transfers'
827    id = Column(String(36), primary_key=True)
828    volume_id = Column(String(36), ForeignKey('volumes.id'), index=True)
829    display_name = Column(String(255))
830    salt = Column(String(255))
831    crypt_hash = Column(String(255))
832    expires_at = Column(DateTime)
833    volume = relationship(Volume, backref="transfer",
834                          foreign_keys=volume_id,
835                          primaryjoin='and_('
836                          'Transfer.volume_id == Volume.id,'
837                          'Transfer.deleted == False)')
838
839
840class DriverInitiatorData(BASE, models.TimestampMixin, models.ModelBase):
841    """Represents private key-value pair specific an initiator for drivers"""
842    __tablename__ = 'driver_initiator_data'
843    __table_args__ = (
844        schema.UniqueConstraint("initiator", "namespace", "key"),
845        CinderBase.__table_args__)
846
847    id = Column(Integer, primary_key=True, nullable=False)
848    initiator = Column(String(255), index=True, nullable=False)
849    namespace = Column(String(255), nullable=False)
850    key = Column(String(255), nullable=False)
851    value = Column(String(255))
852
853
854class Message(BASE, CinderBase):
855    """Represents a message"""
856    __tablename__ = 'messages'
857    id = Column(String(36), primary_key=True, nullable=False)
858    project_id = Column(String(255), nullable=False)
859    # Info/Error/Warning.
860    message_level = Column(String(255), nullable=False)
861    request_id = Column(String(255), nullable=True)
862    resource_type = Column(String(255))
863    # The UUID of the related resource.
864    resource_uuid = Column(String(36), nullable=True)
865    # Operation specific event ID.
866    event_id = Column(String(255), nullable=False)
867    # Message detail ID.
868    detail_id = Column(String(10), nullable=True)
869    # Operation specific action.
870    action_id = Column(String(10), nullable=True)
871    # After this time the message may no longer exist
872    expires_at = Column(DateTime, nullable=True, index=True)
873
874
875class ImageVolumeCacheEntry(BASE, models.ModelBase):
876    """Represents an image volume cache entry"""
877    __tablename__ = 'image_volume_cache_entries'
878
879    id = Column(Integer, primary_key=True, nullable=False)
880    host = Column(String(255), index=True, nullable=False)
881    cluster_name = Column(String(255), nullable=True)
882    image_id = Column(String(36), index=True, nullable=False)
883    image_updated_at = Column(DateTime, nullable=False)
884    volume_id = Column(String(36), nullable=False)
885    size = Column(Integer, nullable=False)
886    last_used = Column(DateTime, default=lambda: timeutils.utcnow())
887
888
889class Worker(BASE, CinderBase):
890    """Represents all resources that are being worked on by a node."""
891    __tablename__ = 'workers'
892    __table_args__ = (schema.UniqueConstraint('resource_type', 'resource_id'),
893                      CinderBase.__table_args__)
894
895    # We want to overwrite default updated_at definition so we timestamp at
896    # creation as well
897    updated_at = Column(DateTime, default=timeutils.utcnow,
898                        onupdate=timeutils.utcnow)
899
900    # Id added for convenience and speed on some operations
901    id = Column(Integer, primary_key=True, autoincrement=True)
902
903    # Type of the resource we are working on (Volume, Snapshot, Backup) it must
904    # match the Versioned Object class name.
905    resource_type = Column(String(40), primary_key=True, nullable=False)
906    # UUID of the resource we are working on
907    resource_id = Column(String(36), primary_key=True, nullable=False)
908
909    # Status that should be cleaned on service failure
910    status = Column(String(255), nullable=False)
911
912    # Service that is currently processing the operation
913    service_id = Column(Integer, nullable=True, index=True)
914
915    # To prevent claiming and updating races
916    race_preventer = Column(Integer, nullable=False, default=0)
917
918    # This is a flag we don't need to store in the DB as it is only used when
919    # we are doing the cleanup to let decorators know
920    cleaning = False
921
922    service = relationship(
923        'Service',
924        backref="workers",
925        foreign_keys=service_id,
926        primaryjoin='Worker.service_id == Service.id')
927
928
929class AttachmentSpecs(BASE, CinderBase):
930    """Represents attachment specs as k/v pairs for a volume_attachment.
931
932    DO NOT USE - NOTHING SHOULD WRITE NEW DATA TO THIS TABLE
933
934    The volume_attachment.connector column should be used instead.
935    """
936
937    __tablename__ = 'attachment_specs'
938    id = Column(Integer, primary_key=True)
939    key = Column(String(255))
940    value = Column(String(255))
941    attachment_id = Column(String(36), ForeignKey('volume_attachment.id'),
942                           nullable=False, index=True)
943    volume_attachment = relationship(
944        VolumeAttachment,
945        backref="attachment_specs",
946        foreign_keys=attachment_id,
947        primaryjoin='and_('
948        'AttachmentSpecs.attachment_id == VolumeAttachment.id,'
949        'AttachmentSpecs.deleted == False)'
950    )
951