Shared filesystem management project for OpenStack.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1315 lines
48 KiB

  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. SQLAlchemy models for Manila data.
  20. """
  21. from oslo_config import cfg
  22. from oslo_db.sqlalchemy import models
  23. from sqlalchemy import Column, Integer, String, schema
  24. from sqlalchemy.ext.declarative import declarative_base
  25. from sqlalchemy import orm
  26. from sqlalchemy import ForeignKey, DateTime, Boolean, Enum
  27. from manila.common import constants
  28. CONF = cfg.CONF
  29. BASE = declarative_base()
  30. class ManilaBase(models.ModelBase,
  31. models.TimestampMixin,
  32. models.SoftDeleteMixin):
  33. """Base class for Manila Models."""
  34. __table_args__ = {'mysql_engine': 'InnoDB'}
  35. metadata = None
  36. def to_dict(self):
  37. model_dict = {}
  38. for k, v in self.items():
  39. if not issubclass(type(v), ManilaBase):
  40. model_dict[k] = v
  41. return model_dict
  42. def soft_delete(self, session, update_status=False,
  43. status_field_name='status'):
  44. """Mark this object as deleted."""
  45. if update_status:
  46. setattr(self, status_field_name, constants.STATUS_DELETED)
  47. return super(ManilaBase, self).soft_delete(session)
  48. class Service(BASE, ManilaBase):
  49. """Represents a running service on a host."""
  50. __tablename__ = 'services'
  51. id = Column(Integer, primary_key=True)
  52. host = Column(String(255)) # , ForeignKey('hosts.id'))
  53. binary = Column(String(255))
  54. topic = Column(String(255))
  55. report_count = Column(Integer, nullable=False, default=0)
  56. disabled = Column(Boolean, default=False)
  57. availability_zone_id = Column(String(36),
  58. ForeignKey('availability_zones.id'),
  59. nullable=True)
  60. availability_zone = orm.relationship(
  61. "AvailabilityZone",
  62. lazy='immediate',
  63. primaryjoin=(
  64. 'and_('
  65. 'Service.availability_zone_id == '
  66. 'AvailabilityZone.id, '
  67. 'AvailabilityZone.deleted == \'False\')'
  68. )
  69. )
  70. class ManilaNode(BASE, ManilaBase):
  71. """Represents a running manila service on a host."""
  72. __tablename__ = 'manila_nodes'
  73. id = Column(Integer, primary_key=True)
  74. service_id = Column(Integer, ForeignKey('services.id'), nullable=True)
  75. class Quota(BASE, ManilaBase):
  76. """Represents a single quota override for a project.
  77. If there is no row for a given project id and resource, then the
  78. default for the quota class is used. If there is no row for a
  79. given quota class and resource, then the default for the
  80. deployment is used. If the row is present but the hard limit is
  81. Null, then the resource is unlimited.
  82. """
  83. __tablename__ = 'quotas'
  84. id = Column(Integer, primary_key=True)
  85. project_id = Column(String(255), index=True)
  86. resource = Column(String(255))
  87. hard_limit = Column(Integer, nullable=True)
  88. class ProjectUserQuota(BASE, ManilaBase):
  89. """Represents a single quota override for a user with in a project."""
  90. __tablename__ = 'project_user_quotas'
  91. id = Column(Integer, primary_key=True, nullable=False)
  92. project_id = Column(String(255), nullable=False)
  93. user_id = Column(String(255), nullable=False)
  94. resource = Column(String(255), nullable=False)
  95. hard_limit = Column(Integer)
  96. class ProjectShareTypeQuota(BASE, ManilaBase):
  97. """Represents a single quota override for a share type within a project."""
  98. __tablename__ = 'project_share_type_quotas'
  99. id = Column(Integer, primary_key=True, nullable=False)
  100. project_id = Column(String(255), nullable=False)
  101. share_type_id = Column(
  102. String(36), ForeignKey('share_types.id'), nullable=False)
  103. resource = Column(String(255), nullable=False)
  104. hard_limit = Column(Integer)
  105. class QuotaClass(BASE, ManilaBase):
  106. """Represents a single quota override for a quota class.
  107. If there is no row for a given quota class and resource, then the
  108. default for the deployment is used. If the row is present but the
  109. hard limit is Null, then the resource is unlimited.
  110. """
  111. __tablename__ = 'quota_classes'
  112. id = Column(Integer, primary_key=True)
  113. class_name = Column(String(255), index=True)
  114. resource = Column(String(255))
  115. hard_limit = Column(Integer, nullable=True)
  116. class QuotaUsage(BASE, ManilaBase):
  117. """Represents the current usage for a given resource."""
  118. __tablename__ = 'quota_usages'
  119. id = Column(Integer, primary_key=True)
  120. project_id = Column(String(255), index=True)
  121. user_id = Column(String(255))
  122. share_type_id = Column(String(36))
  123. resource = Column(String(255))
  124. in_use = Column(Integer)
  125. reserved = Column(Integer)
  126. @property
  127. def total(self):
  128. return self.in_use + self.reserved
  129. until_refresh = Column(Integer, nullable=True)
  130. class Reservation(BASE, ManilaBase):
  131. """Represents a resource reservation for quotas."""
  132. __tablename__ = 'reservations'
  133. id = Column(Integer, primary_key=True)
  134. uuid = Column(String(36), nullable=False)
  135. usage_id = Column(Integer, ForeignKey('quota_usages.id'), nullable=False)
  136. project_id = Column(String(255), index=True)
  137. user_id = Column(String(255))
  138. share_type_id = Column(String(36))
  139. resource = Column(String(255))
  140. delta = Column(Integer)
  141. expire = Column(DateTime, nullable=False)
  142. class Share(BASE, ManilaBase):
  143. """Represents an NFS and CIFS shares."""
  144. __tablename__ = 'shares'
  145. _extra_keys = ['name', 'export_location', 'export_locations', 'status',
  146. 'host', 'share_server_id', 'share_network_id',
  147. 'availability_zone', 'access_rules_status', 'share_type_id']
  148. @property
  149. def name(self):
  150. return CONF.share_name_template % self.id
  151. @property
  152. def export_location(self):
  153. if len(self.instances) > 0:
  154. return self.instance.export_location
  155. @property
  156. def is_busy(self):
  157. # Make sure share is not busy, i.e., not part of a migration
  158. if self.task_state in constants.BUSY_TASK_STATES:
  159. return True
  160. return False
  161. @property
  162. def export_locations(self):
  163. # TODO(gouthamr): Return AZ specific export locations for replicated
  164. # shares.
  165. # NOTE(gouthamr): For a replicated share, export locations of the
  166. # 'active' instances are chosen, if 'available'.
  167. all_export_locations = []
  168. select_instances = list(filter(
  169. lambda x: x['replica_state'] == constants.REPLICA_STATE_ACTIVE,
  170. self.instances)) or self.instances
  171. for instance in select_instances:
  172. if instance['status'] == constants.STATUS_AVAILABLE:
  173. for export_location in instance.export_locations:
  174. all_export_locations.append(export_location['path'])
  175. return all_export_locations
  176. def __getattr__(self, item):
  177. proxified_properties = ('status', 'host', 'share_server_id',
  178. 'share_network_id', 'availability_zone',
  179. 'share_type_id', 'share_type')
  180. if item in proxified_properties:
  181. return getattr(self.instance, item, None)
  182. raise AttributeError(item)
  183. @property
  184. def share_server_id(self):
  185. return self.__getattr__('share_server_id')
  186. @property
  187. def has_replicas(self):
  188. if len(self.instances) > 1:
  189. # NOTE(gouthamr): The 'primary' instance of a replicated share
  190. # has a 'replica_state' set to 'active'. Only the secondary replica
  191. # instances need to be regarded as true 'replicas' by users.
  192. replicas = (list(filter(lambda x: x['replica_state'] is not None,
  193. self.instances)))
  194. return len(replicas) > 1
  195. return False
  196. @property
  197. def instance(self):
  198. # NOTE(gouthamr): The order of preference: status 'replication_change',
  199. # followed by 'available' and 'error'. If replicated share and
  200. # not undergoing a 'replication_change', only 'active' instances are
  201. # preferred.
  202. result = None
  203. if len(self.instances) > 0:
  204. order = (constants.STATUS_REVERTING,
  205. constants.STATUS_REPLICATION_CHANGE,
  206. constants.STATUS_MIGRATING, constants.STATUS_AVAILABLE,
  207. constants.STATUS_ERROR)
  208. other_statuses = (
  209. [x['status'] for x in self.instances if
  210. x['status'] not in order and
  211. x['status'] not in constants.TRANSITIONAL_STATUSES]
  212. )
  213. order = (order + tuple(other_statuses) +
  214. constants.TRANSITIONAL_STATUSES)
  215. sorted_instances = sorted(
  216. self.instances, key=lambda x: order.index(x['status']))
  217. select_instances = sorted_instances
  218. if (select_instances[0]['status'] !=
  219. constants.STATUS_REPLICATION_CHANGE):
  220. select_instances = (
  221. list(filter(lambda x: x['replica_state'] ==
  222. constants.REPLICA_STATE_ACTIVE,
  223. sorted_instances)) or sorted_instances
  224. )
  225. result = select_instances[0]
  226. return result
  227. @property
  228. def access_rules_status(self):
  229. return get_access_rules_status(self.instances)
  230. id = Column(String(36), primary_key=True)
  231. deleted = Column(String(36), default='False')
  232. user_id = Column(String(255))
  233. project_id = Column(String(255))
  234. size = Column(Integer)
  235. display_name = Column(String(255))
  236. display_description = Column(String(255))
  237. snapshot_id = Column(String(36))
  238. snapshot_support = Column(Boolean, default=True)
  239. create_share_from_snapshot_support = Column(Boolean, default=True)
  240. revert_to_snapshot_support = Column(Boolean, default=False)
  241. replication_type = Column(String(255), nullable=True)
  242. mount_snapshot_support = Column(Boolean, default=False)
  243. share_proto = Column(String(255))
  244. is_public = Column(Boolean, default=False)
  245. share_group_id = Column(String(36),
  246. ForeignKey('share_groups.id'),
  247. nullable=True)
  248. source_share_group_snapshot_member_id = Column(String(36), nullable=True)
  249. task_state = Column(String(255))
  250. instances = orm.relationship(
  251. "ShareInstance",
  252. lazy='subquery',
  253. primaryjoin=(
  254. 'and_('
  255. 'Share.id == ShareInstance.share_id, '
  256. 'ShareInstance.deleted == "False")'
  257. ),
  258. viewonly=True,
  259. join_depth=2,
  260. )
  261. class ShareInstance(BASE, ManilaBase):
  262. __tablename__ = 'share_instances'
  263. _extra_keys = ['name', 'export_location', 'availability_zone',
  264. 'replica_state']
  265. _proxified_properties = ('user_id', 'project_id', 'size',
  266. 'display_name', 'display_description',
  267. 'snapshot_id', 'share_proto', 'is_public',
  268. 'share_group_id', 'replication_type',
  269. 'source_share_group_snapshot_member_id',
  270. 'mount_snapshot_support')
  271. def set_share_data(self, share):
  272. for share_property in self._proxified_properties:
  273. setattr(self, share_property, share[share_property])
  274. @property
  275. def name(self):
  276. return CONF.share_name_template % self.id
  277. @property
  278. def export_location(self):
  279. if len(self.export_locations) > 0:
  280. return self.export_locations[0]['path']
  281. @property
  282. def availability_zone(self):
  283. if self._availability_zone:
  284. return self._availability_zone['name']
  285. id = Column(String(36), primary_key=True)
  286. share_id = Column(String(36), ForeignKey('shares.id'))
  287. deleted = Column(String(36), default='False')
  288. host = Column(String(255))
  289. status = Column(String(255))
  290. ACCESS_STATUS_PRIORITIES = {
  291. constants.STATUS_ACTIVE: 0,
  292. constants.SHARE_INSTANCE_RULES_SYNCING: 1,
  293. constants.SHARE_INSTANCE_RULES_ERROR: 2,
  294. }
  295. access_rules_status = Column(Enum(constants.STATUS_ACTIVE,
  296. constants.SHARE_INSTANCE_RULES_SYNCING,
  297. constants.SHARE_INSTANCE_RULES_ERROR),
  298. default=constants.STATUS_ACTIVE)
  299. scheduled_at = Column(DateTime)
  300. launched_at = Column(DateTime)
  301. terminated_at = Column(DateTime)
  302. replica_state = Column(String(255), nullable=True)
  303. cast_rules_to_readonly = Column(Boolean, default=False, nullable=False)
  304. share_type_id = Column(String(36), ForeignKey('share_types.id'),
  305. nullable=True)
  306. availability_zone_id = Column(String(36),
  307. ForeignKey('availability_zones.id'),
  308. nullable=True)
  309. _availability_zone = orm.relationship(
  310. "AvailabilityZone",
  311. lazy='subquery',
  312. foreign_keys=availability_zone_id,
  313. primaryjoin=(
  314. 'and_('
  315. 'ShareInstance.availability_zone_id == '
  316. 'AvailabilityZone.id, '
  317. 'AvailabilityZone.deleted == \'False\')'
  318. )
  319. )
  320. export_locations = orm.relationship(
  321. "ShareInstanceExportLocations",
  322. lazy='joined',
  323. backref=orm.backref('share_instance', lazy='joined'),
  324. primaryjoin=(
  325. 'and_('
  326. 'ShareInstance.id == '
  327. 'ShareInstanceExportLocations.share_instance_id, '
  328. 'ShareInstanceExportLocations.deleted == 0)'
  329. )
  330. )
  331. share_network_id = Column(String(36), ForeignKey('share_networks.id'),
  332. nullable=True)
  333. share_server_id = Column(String(36), ForeignKey('share_servers.id'),
  334. nullable=True)
  335. share_type = orm.relationship(
  336. "ShareTypes",
  337. lazy='subquery',
  338. foreign_keys=share_type_id,
  339. primaryjoin='and_('
  340. 'ShareInstance.share_type_id == ShareTypes.id, '
  341. 'ShareTypes.deleted == "False")')
  342. class ShareInstanceExportLocations(BASE, ManilaBase):
  343. """Represents export locations of share instances."""
  344. __tablename__ = 'share_instance_export_locations'
  345. _extra_keys = ['el_metadata', ]
  346. @property
  347. def el_metadata(self):
  348. el_metadata = {}
  349. for meta in self._el_metadata_bare: # pylint: disable=no-member
  350. el_metadata[meta['key']] = meta['value']
  351. return el_metadata
  352. @property
  353. def replica_state(self):
  354. return self.share_instance['replica_state']
  355. id = Column(Integer, primary_key=True)
  356. uuid = Column(String(36), nullable=False, unique=True)
  357. share_instance_id = Column(
  358. String(36), ForeignKey('share_instances.id'), nullable=False)
  359. path = Column(String(2000))
  360. is_admin_only = Column(Boolean, default=False, nullable=False)
  361. class ShareInstanceExportLocationsMetadata(BASE, ManilaBase):
  362. """Represents export location metadata of share instances."""
  363. __tablename__ = "share_instance_export_locations_metadata"
  364. _extra_keys = ['export_location_uuid', ]
  365. id = Column(Integer, primary_key=True)
  366. export_location_id = Column(
  367. Integer,
  368. ForeignKey("share_instance_export_locations.id"), nullable=False)
  369. key = Column(String(255), nullable=False)
  370. value = Column(String(1023), nullable=False)
  371. export_location = orm.relationship(
  372. ShareInstanceExportLocations,
  373. backref="_el_metadata_bare",
  374. foreign_keys=export_location_id,
  375. lazy='immediate',
  376. primaryjoin="and_("
  377. "%(cls_name)s.export_location_id == "
  378. "ShareInstanceExportLocations.id,"
  379. "%(cls_name)s.deleted == 0)" % {
  380. "cls_name": "ShareInstanceExportLocationsMetadata"})
  381. @property
  382. def export_location_uuid(self):
  383. return self.export_location.uuid # pylint: disable=no-member
  384. class ShareTypes(BASE, ManilaBase):
  385. """Represent possible share_types of volumes offered."""
  386. __tablename__ = "share_types"
  387. id = Column(String(36), primary_key=True)
  388. deleted = Column(String(36), default='False')
  389. name = Column(String(255))
  390. description = Column(String(255))
  391. is_public = Column(Boolean, default=True)
  392. class ShareTypeProjects(BASE, ManilaBase):
  393. """Represent projects associated share_types."""
  394. __tablename__ = "share_type_projects"
  395. __table_args__ = (schema.UniqueConstraint(
  396. "share_type_id", "project_id", "deleted",
  397. name="uniq_share_type_projects0share_type_id0project_id0deleted"),
  398. )
  399. id = Column(Integer, primary_key=True)
  400. share_type_id = Column(Integer, ForeignKey('share_types.id'),
  401. nullable=False)
  402. project_id = Column(String(255))
  403. share_type = orm.relationship(
  404. ShareTypes,
  405. backref="projects",
  406. foreign_keys=share_type_id,
  407. primaryjoin='and_('
  408. 'ShareTypeProjects.share_type_id == ShareTypes.id,'
  409. 'ShareTypeProjects.deleted == 0)')
  410. class ShareTypeExtraSpecs(BASE, ManilaBase):
  411. """Represents additional specs as key/value pairs for a share_type."""
  412. __tablename__ = 'share_type_extra_specs'
  413. id = Column(Integer, primary_key=True)
  414. key = Column("spec_key", String(255))
  415. value = Column("spec_value", String(255))
  416. share_type_id = Column(String(36), ForeignKey('share_types.id'),
  417. nullable=False)
  418. share_type = orm.relationship(
  419. ShareTypes,
  420. backref="extra_specs",
  421. foreign_keys=share_type_id,
  422. primaryjoin='and_('
  423. 'ShareTypeExtraSpecs.share_type_id == ShareTypes.id,'
  424. 'ShareTypeExtraSpecs.deleted == 0)'
  425. )
  426. class ShareMetadata(BASE, ManilaBase):
  427. """Represents a metadata key/value pair for a share."""
  428. __tablename__ = 'share_metadata'
  429. id = Column(Integer, primary_key=True)
  430. key = Column(String(255), nullable=False)
  431. value = Column(String(1023), nullable=False)
  432. share_id = Column(String(36), ForeignKey('shares.id'), nullable=False)
  433. share = orm.relationship(Share, backref="share_metadata",
  434. foreign_keys=share_id,
  435. primaryjoin='and_('
  436. 'ShareMetadata.share_id == Share.id,'
  437. 'ShareMetadata.deleted == 0)')
  438. class ShareAccessMapping(BASE, ManilaBase):
  439. """Represents access to share."""
  440. __tablename__ = 'share_access_map'
  441. id = Column(String(36), primary_key=True)
  442. deleted = Column(String(36), default='False')
  443. share_id = Column(String(36), ForeignKey('shares.id'))
  444. access_type = Column(String(255))
  445. access_to = Column(String(255))
  446. access_key = Column(String(255), nullable=True)
  447. access_level = Column(Enum(*constants.ACCESS_LEVELS),
  448. default=constants.ACCESS_LEVEL_RW)
  449. @property
  450. def state(self):
  451. """Get the aggregated 'state' from all the instance mapping states.
  452. An access rule is supposed to be truly 'active' when it has been
  453. applied across all of the share instances of the parent share object.
  454. """
  455. return get_aggregated_access_rules_state(self.instance_mappings)
  456. instance_mappings = orm.relationship(
  457. "ShareInstanceAccessMapping",
  458. lazy='immediate',
  459. primaryjoin=(
  460. 'and_('
  461. 'ShareAccessMapping.id == '
  462. 'ShareInstanceAccessMapping.access_id, '
  463. 'ShareInstanceAccessMapping.deleted == "False")'
  464. )
  465. )
  466. class ShareAccessRulesMetadata(BASE, ManilaBase):
  467. """Represents a metadata key/value pair for a share access rule."""
  468. __tablename__ = 'share_access_rules_metadata'
  469. id = Column(Integer, primary_key=True)
  470. deleted = Column(String(36), default='False')
  471. key = Column(String(255), nullable=False)
  472. value = Column(String(1023), nullable=False)
  473. access_id = Column(String(36), ForeignKey('share_access_map.id'),
  474. nullable=False)
  475. access = orm.relationship(
  476. ShareAccessMapping, backref="share_access_rules_metadata",
  477. foreign_keys=access_id,
  478. lazy='immediate',
  479. primaryjoin='and_('
  480. 'ShareAccessRulesMetadata.access_id == ShareAccessMapping.id,'
  481. 'ShareAccessRulesMetadata.deleted == "False")')
  482. class ShareInstanceAccessMapping(BASE, ManilaBase):
  483. """Represents access to individual share instances."""
  484. __tablename__ = 'share_instance_access_map'
  485. _proxified_properties = ('share_id', 'access_type', 'access_key',
  486. 'access_to', 'access_level')
  487. def set_share_access_data(self, share_access):
  488. for share_access_attr in self._proxified_properties:
  489. setattr(self, share_access_attr, share_access[share_access_attr])
  490. id = Column(String(36), primary_key=True)
  491. deleted = Column(String(36), default='False')
  492. share_instance_id = Column(String(36), ForeignKey('share_instances.id'))
  493. access_id = Column(String(36), ForeignKey('share_access_map.id'))
  494. state = Column(String(255), default=constants.ACCESS_STATE_QUEUED_TO_APPLY)
  495. instance = orm.relationship(
  496. "ShareInstance",
  497. lazy='immediate',
  498. primaryjoin=(
  499. 'and_('
  500. 'ShareInstanceAccessMapping.share_instance_id == '
  501. 'ShareInstance.id, '
  502. 'ShareInstanceAccessMapping.deleted == "False")'
  503. )
  504. )
  505. class ShareSnapshot(BASE, ManilaBase):
  506. """Represents a snapshot of a share."""
  507. __tablename__ = 'share_snapshots'
  508. _extra_keys = ['name', 'share_name', 'status', 'progress',
  509. 'provider_location', 'aggregate_status']
  510. def __getattr__(self, item):
  511. proxified_properties = ('status', 'progress', 'provider_location')
  512. if item in proxified_properties:
  513. return getattr(self.instance, item, None)
  514. raise AttributeError(item)
  515. @property
  516. def export_locations(self):
  517. # TODO(gouthamr): Return AZ specific export locations for replicated
  518. # snapshots.
  519. # NOTE(gouthamr): For a replicated snapshot, export locations of the
  520. # 'active' instances are chosen, if 'available'.
  521. all_export_locations = []
  522. select_instances = list(filter(
  523. lambda x: (x['share_instance']['replica_state'] ==
  524. constants.REPLICA_STATE_ACTIVE),
  525. self.instances)) or self.instances
  526. for instance in select_instances:
  527. if instance['status'] == constants.STATUS_AVAILABLE:
  528. for export_location in instance.export_locations:
  529. all_export_locations.append(export_location)
  530. return all_export_locations
  531. @property
  532. def name(self):
  533. return CONF.share_snapshot_name_template % self.id
  534. @property
  535. def share_name(self):
  536. return CONF.share_name_template % self.share_id
  537. @property
  538. def instance(self):
  539. result = None
  540. if len(self.instances) > 0:
  541. def qualified_replica(x):
  542. preferred_statuses = (constants.REPLICA_STATE_ACTIVE,)
  543. return x['replica_state'] in preferred_statuses
  544. replica_snapshots = list(filter(
  545. lambda x: qualified_replica(x.share_instance), self.instances))
  546. migrating_snapshots = list(filter(
  547. lambda x: x.share_instance['status'] ==
  548. constants.STATUS_MIGRATING, self.instances))
  549. snapshot_instances = (replica_snapshots or migrating_snapshots
  550. or self.instances)
  551. result = snapshot_instances[0]
  552. return result
  553. @property
  554. def aggregate_status(self):
  555. """Get the aggregated 'status' of all instances.
  556. A snapshot is supposed to be truly 'available' when it is available
  557. across all of the share instances of the parent share object. In
  558. case of replication, we only consider replicas (share instances)
  559. that are in 'in_sync' replica_state.
  560. """
  561. def qualified_replica(x):
  562. preferred_statuses = (constants.REPLICA_STATE_ACTIVE,
  563. constants.REPLICA_STATE_IN_SYNC)
  564. return x['replica_state'] in preferred_statuses
  565. replica_snapshots = list(filter(
  566. lambda x: qualified_replica(x['share_instance']), self.instances))
  567. if not replica_snapshots:
  568. return self.status
  569. order = (constants.STATUS_DELETING, constants.STATUS_CREATING,
  570. constants.STATUS_ERROR, constants.STATUS_MIGRATING,
  571. constants.STATUS_AVAILABLE)
  572. other_statuses = [x['status'] for x in self.instances if
  573. x['status'] not in order]
  574. order = (order + tuple(other_statuses))
  575. sorted_instances = sorted(
  576. replica_snapshots, key=lambda x: order.index(x['status']))
  577. return sorted_instances[0].status
  578. id = Column(String(36), primary_key=True)
  579. deleted = Column(String(36), default='False')
  580. user_id = Column(String(255))
  581. project_id = Column(String(255))
  582. share_id = Column(String(36))
  583. size = Column(Integer)
  584. display_name = Column(String(255))
  585. display_description = Column(String(255))
  586. share_size = Column(Integer)
  587. share_proto = Column(String(255))
  588. share = orm.relationship(Share, backref="snapshots",
  589. foreign_keys=share_id,
  590. primaryjoin='and_('
  591. 'ShareSnapshot.share_id == Share.id,'
  592. 'ShareSnapshot.deleted == "False")')
  593. class ShareSnapshotInstance(BASE, ManilaBase):
  594. """Represents a snapshot of a share."""
  595. __tablename__ = 'share_snapshot_instances'
  596. _extra_keys = ['name', 'share_id', 'share_name']
  597. @property
  598. def name(self):
  599. return CONF.share_snapshot_name_template % self.id
  600. @property
  601. def share_name(self):
  602. return CONF.share_name_template % self.share_instance_id
  603. @property
  604. def share_id(self):
  605. # NOTE(u_glide): This property required for compatibility
  606. # with share drivers
  607. return self.share_instance_id
  608. @property
  609. def size(self):
  610. # NOTE(silvacarlose) for backwards compatibility
  611. if self.instance_size is None:
  612. return self.snapshot.size
  613. else:
  614. return self.instance_size
  615. id = Column(String(36), primary_key=True)
  616. deleted = Column(String(36), default='False')
  617. snapshot_id = Column(String(36), nullable=True)
  618. share_instance_id = Column(
  619. String(36), ForeignKey('share_instances.id'), nullable=False)
  620. status = Column(String(255))
  621. progress = Column(String(255))
  622. provider_location = Column(String(255))
  623. share_proto = Column(String(255))
  624. instance_size = Column('size', Integer)
  625. share_group_snapshot_id = Column(String(36), nullable=True)
  626. user_id = Column(String(255))
  627. project_id = Column(String(255))
  628. export_locations = orm.relationship(
  629. "ShareSnapshotInstanceExportLocation",
  630. lazy='immediate',
  631. primaryjoin=(
  632. 'and_('
  633. 'ShareSnapshotInstance.id == '
  634. 'ShareSnapshotInstanceExportLocation.share_snapshot_instance_id, '
  635. 'ShareSnapshotInstanceExportLocation.deleted == "False")'
  636. )
  637. )
  638. share_instance = orm.relationship(
  639. ShareInstance, backref="snapshot_instances",
  640. lazy='immediate',
  641. primaryjoin=(
  642. 'and_('
  643. 'ShareSnapshotInstance.share_instance_id == ShareInstance.id,'
  644. 'ShareSnapshotInstance.deleted == "False")')
  645. )
  646. snapshot = orm.relationship(
  647. "ShareSnapshot",
  648. lazy="immediate",
  649. foreign_keys=snapshot_id,
  650. backref="instances",
  651. primaryjoin=(
  652. 'and_('
  653. 'ShareSnapshot.id == ShareSnapshotInstance.snapshot_id, '
  654. 'ShareSnapshotInstance.deleted == "False")'
  655. ),
  656. viewonly=True,
  657. join_depth=2,
  658. )
  659. share_group_snapshot = orm.relationship(
  660. "ShareGroupSnapshot",
  661. lazy="immediate",
  662. foreign_keys=share_group_snapshot_id,
  663. backref="share_group_snapshot_members",
  664. primaryjoin=('ShareGroupSnapshot.id == '
  665. 'ShareSnapshotInstance.share_group_snapshot_id'),
  666. viewonly=True,
  667. join_depth=2,
  668. )
  669. class ShareSnapshotAccessMapping(BASE, ManilaBase):
  670. """Represents access to share snapshot."""
  671. __tablename__ = 'share_snapshot_access_map'
  672. @property
  673. def state(self):
  674. """Get the aggregated 'state' from all the instance mapping states.
  675. An access rule is supposed to be truly 'active' when it has been
  676. applied across all of the share snapshot instances of the parent
  677. share snapshot object.
  678. """
  679. return get_aggregated_access_rules_state(self.instance_mappings)
  680. id = Column(String(36), primary_key=True)
  681. deleted = Column(String(36), default='False')
  682. share_snapshot_id = Column(String(36), ForeignKey('share_snapshots.id'))
  683. access_type = Column(String(255))
  684. access_to = Column(String(255))
  685. instance_mappings = orm.relationship(
  686. "ShareSnapshotInstanceAccessMapping",
  687. lazy='immediate',
  688. primaryjoin=(
  689. 'and_('
  690. 'ShareSnapshotAccessMapping.id == '
  691. 'ShareSnapshotInstanceAccessMapping.access_id, '
  692. 'ShareSnapshotInstanceAccessMapping.deleted == "False")'
  693. )
  694. )
  695. class ShareSnapshotInstanceAccessMapping(BASE, ManilaBase):
  696. """Represents access to individual share snapshot instances."""
  697. __tablename__ = 'share_snapshot_instance_access_map'
  698. _proxified_properties = ('share_snapshot_id', 'access_type', 'access_to')
  699. def set_snapshot_access_data(self, snapshot_access):
  700. for snapshot_access_attr in self._proxified_properties:
  701. setattr(self, snapshot_access_attr,
  702. snapshot_access[snapshot_access_attr])
  703. id = Column(String(36), primary_key=True)
  704. deleted = Column(String(36), default='False')
  705. share_snapshot_instance_id = Column(String(36), ForeignKey(
  706. 'share_snapshot_instances.id'))
  707. access_id = Column(String(36), ForeignKey('share_snapshot_access_map.id'))
  708. state = Column(Enum(*constants.ACCESS_RULES_STATES),
  709. default=constants.ACCESS_STATE_QUEUED_TO_APPLY)
  710. instance = orm.relationship(
  711. "ShareSnapshotInstance",
  712. lazy='immediate',
  713. primaryjoin=(
  714. 'and_('
  715. 'ShareSnapshotInstanceAccessMapping.share_snapshot_instance_id == '
  716. 'ShareSnapshotInstance.id, '
  717. 'ShareSnapshotInstanceAccessMapping.deleted == "False")'
  718. )
  719. )
  720. class ShareSnapshotInstanceExportLocation(BASE, ManilaBase):
  721. """Represents export locations of share snapshot instances."""
  722. __tablename__ = 'share_snapshot_instance_export_locations'
  723. id = Column(String(36), primary_key=True)
  724. share_snapshot_instance_id = Column(
  725. String(36), ForeignKey('share_snapshot_instances.id'), nullable=False)
  726. path = Column(String(2000))
  727. is_admin_only = Column(Boolean, default=False, nullable=False)
  728. deleted = Column(String(36), default='False')
  729. class SecurityService(BASE, ManilaBase):
  730. """Security service information for manila shares."""
  731. __tablename__ = 'security_services'
  732. id = Column(String(36), primary_key=True)
  733. deleted = Column(String(36), default='False')
  734. project_id = Column(String(255), nullable=False)
  735. type = Column(String(32), nullable=False)
  736. dns_ip = Column(String(64), nullable=True)
  737. server = Column(String(255), nullable=True)
  738. domain = Column(String(255), nullable=True)
  739. user = Column(String(255), nullable=True)
  740. password = Column(String(255), nullable=True)
  741. name = Column(String(255), nullable=True)
  742. description = Column(String(255), nullable=True)
  743. ou = Column(String(255), nullable=True)
  744. class ShareNetwork(BASE, ManilaBase):
  745. """Represents network data used by share."""
  746. __tablename__ = 'share_networks'
  747. id = Column(String(36), primary_key=True, nullable=False)
  748. deleted = Column(String(36), default='False')
  749. project_id = Column(String(255), nullable=False)
  750. user_id = Column(String(255), nullable=False)
  751. neutron_net_id = Column(String(36), nullable=True)
  752. neutron_subnet_id = Column(String(36), nullable=True)
  753. network_type = Column(String(32), nullable=True)
  754. segmentation_id = Column(Integer, nullable=True)
  755. cidr = Column(String(64), nullable=True)
  756. gateway = Column(String(64), nullable=True)
  757. mtu = Column(Integer, nullable=True)
  758. ip_version = Column(Integer, nullable=True)
  759. name = Column(String(255), nullable=True)
  760. description = Column(String(255), nullable=True)
  761. security_services = orm.relationship(
  762. "SecurityService",
  763. secondary="share_network_security_service_association",
  764. backref="share_networks",
  765. primaryjoin='and_('
  766. 'ShareNetwork.id == '
  767. 'ShareNetworkSecurityServiceAssociation.share_network_id,'
  768. 'ShareNetworkSecurityServiceAssociation.deleted == 0,'
  769. 'ShareNetwork.deleted == "False")',
  770. secondaryjoin='and_('
  771. 'SecurityService.id == '
  772. 'ShareNetworkSecurityServiceAssociation.security_service_id,'
  773. 'SecurityService.deleted == "False")')
  774. share_instances = orm.relationship(
  775. "ShareInstance",
  776. backref='share_network',
  777. primaryjoin='and_('
  778. 'ShareNetwork.id == ShareInstance.share_network_id,'
  779. 'ShareInstance.deleted == "False")')
  780. share_servers = orm.relationship(
  781. "ShareServer", backref='share_network',
  782. primaryjoin='and_(ShareNetwork.id == ShareServer.share_network_id,'
  783. 'ShareServer.deleted == "False")')
  784. class ShareServer(BASE, ManilaBase):
  785. """Represents share server used by share."""
  786. __tablename__ = 'share_servers'
  787. id = Column(String(36), primary_key=True, nullable=False)
  788. deleted = Column(String(36), default='False')
  789. share_network_id = Column(String(36), ForeignKey('share_networks.id'),
  790. nullable=True)
  791. host = Column(String(255), nullable=False)
  792. is_auto_deletable = Column(Boolean, default=True)
  793. identifier = Column(String(255), nullable=True)
  794. status = Column(Enum(
  795. constants.STATUS_INACTIVE, constants.STATUS_ACTIVE,
  796. constants.STATUS_ERROR, constants.STATUS_DELETING,
  797. constants.STATUS_CREATING, constants.STATUS_DELETED,
  798. constants.STATUS_MANAGING, constants.STATUS_UNMANAGING,
  799. constants.STATUS_UNMANAGE_ERROR, constants.STATUS_MANAGE_ERROR),
  800. default=constants.STATUS_INACTIVE)
  801. network_allocations = orm.relationship(
  802. "NetworkAllocation",
  803. primaryjoin='and_('
  804. 'ShareServer.id == NetworkAllocation.share_server_id,'
  805. 'NetworkAllocation.deleted == "False")')
  806. share_instances = orm.relationship(
  807. "ShareInstance",
  808. backref='share_server',
  809. primaryjoin='and_('
  810. 'ShareServer.id == ShareInstance.share_server_id,'
  811. 'ShareInstance.deleted == "False")')
  812. share_groups = orm.relationship(
  813. "ShareGroup", backref='share_server', primaryjoin='and_('
  814. 'ShareServer.id == ShareGroup.share_server_id,'
  815. 'ShareGroup.deleted == "False")')
  816. _backend_details = orm.relationship(
  817. "ShareServerBackendDetails",
  818. lazy='immediate',
  819. viewonly=True,
  820. primaryjoin='and_('
  821. 'ShareServer.id == '
  822. 'ShareServerBackendDetails.share_server_id, '
  823. 'ShareServerBackendDetails.deleted == "False")')
  824. @property
  825. def backend_details(self):
  826. return {model['key']: model['value']
  827. for model in self._backend_details}
  828. _extra_keys = ['backend_details']
  829. class ShareServerBackendDetails(BASE, ManilaBase):
  830. """Represents a metadata key/value pair for a share server."""
  831. __tablename__ = 'share_server_backend_details'
  832. deleted = Column(String(36), default='False')
  833. id = Column(Integer, primary_key=True)
  834. key = Column(String(255), nullable=False)
  835. value = Column(String(1023), nullable=False)
  836. share_server_id = Column(String(36), ForeignKey('share_servers.id'),
  837. nullable=False)
  838. class ShareNetworkSecurityServiceAssociation(BASE, ManilaBase):
  839. """Association table between compute_zones and compute_nodes tables."""
  840. __tablename__ = 'share_network_security_service_association'
  841. id = Column(Integer, primary_key=True)
  842. share_network_id = Column(String(36),
  843. ForeignKey('share_networks.id'),
  844. nullable=False)
  845. security_service_id = Column(String(36),
  846. ForeignKey('security_services.id'),
  847. nullable=False)
  848. class NetworkAllocation(BASE, ManilaBase):
  849. """Represents network allocation data."""
  850. __tablename__ = 'network_allocations'
  851. id = Column(String(36), primary_key=True, nullable=False)
  852. deleted = Column(String(36), default='False')
  853. label = Column(String(255), nullable=True)
  854. ip_address = Column(String(64), nullable=True)
  855. ip_version = Column(Integer, nullable=True)
  856. cidr = Column(String(64), nullable=True)
  857. gateway = Column(String(64), nullable=True)
  858. mtu = Column(Integer, nullable=True)
  859. network_type = Column(String(32), nullable=True)
  860. segmentation_id = Column(Integer, nullable=True)
  861. mac_address = Column(String(32), nullable=True)
  862. share_server_id = Column(String(36), ForeignKey('share_servers.id'),
  863. nullable=False)
  864. class DriverPrivateData(BASE, ManilaBase):
  865. """Represents a private data as key-value pairs for a driver."""
  866. __tablename__ = 'drivers_private_data'
  867. entity_uuid = Column(String(36), nullable=False, primary_key=True)
  868. key = Column(String(255), nullable=False, primary_key=True)
  869. value = Column(String(1023), nullable=False)
  870. class AvailabilityZone(BASE, ManilaBase):
  871. """Represents a private data as key-value pairs for a driver."""
  872. __tablename__ = 'availability_zones'
  873. id = Column(String(36), primary_key=True, nullable=False)
  874. deleted = Column(String(36), default='False')
  875. name = Column(String(255), nullable=False)
  876. class ShareGroupTypes(BASE, ManilaBase):
  877. """Represent possible share group types of shares offered."""
  878. __tablename__ = "share_group_types"
  879. __table_args__ = (
  880. schema.UniqueConstraint(
  881. "name", "deleted", name="uniq_share_group_type_name"),
  882. )
  883. id = Column(String(36), primary_key=True)
  884. deleted = Column(String(36), default='False')
  885. name = Column(String(255))
  886. is_public = Column(Boolean, default=True)
  887. class ShareGroup(BASE, ManilaBase):
  888. """Represents a share group."""
  889. __tablename__ = 'share_groups'
  890. _extra_keys = [
  891. 'availability_zone',
  892. ]
  893. id = Column(String(36), primary_key=True)
  894. user_id = Column(String(255), nullable=False)
  895. project_id = Column(String(255), nullable=False)
  896. deleted = Column(String(36), default='False')
  897. host = Column(String(255))
  898. name = Column(String(255))
  899. description = Column(String(255))
  900. status = Column(String(255))
  901. source_share_group_snapshot_id = Column(String(36))
  902. share_network_id = Column(
  903. String(36), ForeignKey('share_networks.id'), nullable=True)
  904. share_server_id = Column(
  905. String(36), ForeignKey('share_servers.id'), nullable=True)
  906. share_group_type_id = Column(
  907. String(36), ForeignKey('share_group_types.id'), nullable=True)
  908. availability_zone_id = Column(
  909. String(36), ForeignKey('availability_zones.id'), nullable=True)
  910. consistent_snapshot_support = Column(Enum('pool', 'host'), default=None)
  911. share_group_type = orm.relationship(
  912. ShareGroupTypes,
  913. backref="share_groups",
  914. foreign_keys=share_group_type_id,
  915. primaryjoin="and_("
  916. "ShareGroup.share_group_type_id =="
  917. "ShareGroupTypes.id,"
  918. "ShareGroup.deleted == 'False')")
  919. _availability_zone = orm.relationship(
  920. "AvailabilityZone",
  921. lazy='immediate',
  922. foreign_keys=availability_zone_id,
  923. primaryjoin=(
  924. "and_("
  925. "ShareGroup.availability_zone_id == AvailabilityZone.id, "
  926. "AvailabilityZone.deleted == 'False')"))
  927. @property
  928. def availability_zone(self):
  929. if self._availability_zone:
  930. return self._availability_zone['name']
  931. class ShareGroupTypeProjects(BASE, ManilaBase):
  932. """Represent projects associated share group types."""
  933. __tablename__ = "share_group_type_projects"
  934. __table_args__ = (schema.UniqueConstraint(
  935. "share_group_type_id", "project_id", "deleted",
  936. name=("uniq_share_group_type_projects0share_group_type_id"
  937. "0project_id0deleted")),
  938. )
  939. id = Column(Integer, primary_key=True)
  940. share_group_type_id = Column(
  941. String, ForeignKey('share_group_types.id'), nullable=False)
  942. project_id = Column(String(255))
  943. share_group_type = orm.relationship(
  944. ShareGroupTypes,
  945. backref="projects",
  946. foreign_keys=share_group_type_id,
  947. primaryjoin='and_('
  948. 'ShareGroupTypeProjects.share_group_type_id == '
  949. 'ShareGroupTypes.id,'
  950. 'ShareGroupTypeProjects.deleted == 0)')
  951. class ShareGroupTypeSpecs(BASE, ManilaBase):
  952. """Represents additional specs for a share group type."""
  953. __tablename__ = 'share_group_type_specs'
  954. id = Column(Integer, primary_key=True)
  955. key = Column("spec_key", String(255))
  956. value = Column("spec_value", String(255))
  957. share_group_type_id = Column(
  958. String(36), ForeignKey('share_group_types.id'), nullable=False)
  959. share_group_type = orm.relationship(
  960. ShareGroupTypes,
  961. backref="group_specs",
  962. foreign_keys=share_group_type_id,
  963. primaryjoin='and_('
  964. 'ShareGroupTypeSpecs.share_group_type_id == ShareGroupTypes.id,'
  965. 'ShareGroupTypeSpecs.deleted == 0)'
  966. )
  967. class ShareGroupSnapshot(BASE, ManilaBase):
  968. """Represents a share group snapshot."""
  969. __tablename__ = 'share_group_snapshots'
  970. id = Column(String(36), primary_key=True)
  971. share_group_id = Column(String(36), ForeignKey('share_groups.id'))
  972. user_id = Column(String(255), nullable=False)
  973. project_id = Column(String(255), nullable=False)
  974. deleted = Column(String(36), default='False')
  975. name = Column(String(255))
  976. description = Column(String(255))
  977. status = Column(String(255))
  978. share_group = orm.relationship(
  979. ShareGroup,
  980. backref="snapshots",
  981. foreign_keys=share_group_id,
  982. primaryjoin=('and_('
  983. 'ShareGroupSnapshot.share_group_id == ShareGroup.id,'
  984. 'ShareGroupSnapshot.deleted == "False")')
  985. )
  986. class ShareGroupTypeShareTypeMapping(BASE, ManilaBase):
  987. """Represents the share types supported by a share group type."""
  988. __tablename__ = 'share_group_type_share_type_mappings'
  989. id = Column(String(36), primary_key=True)
  990. deleted = Column(String(36), default='False')
  991. share_group_type_id = Column(
  992. String(36), ForeignKey('share_group_types.id'), nullable=False)
  993. share_type_id = Column(
  994. String(36), ForeignKey('share_types.id'), nullable=False)
  995. share_group_type = orm.relationship(
  996. ShareGroupTypes,
  997. backref="share_types",
  998. foreign_keys=share_group_type_id,
  999. primaryjoin=('and_('
  1000. 'ShareGroupTypeShareTypeMapping.share_group_type_id '
  1001. '== ShareGroupTypes.id,'
  1002. 'ShareGroupTypeShareTypeMapping.deleted == "False")')
  1003. )
  1004. class ShareGroupShareTypeMapping(BASE, ManilaBase):
  1005. """Represents the share types in a share group."""
  1006. __tablename__ = 'share_group_share_type_mappings'
  1007. id = Column(String(36), primary_key=True)
  1008. deleted = Column(String(36), default='False')
  1009. share_group_id = Column(
  1010. String(36), ForeignKey('share_groups.id'), nullable=False)
  1011. share_type_id = Column(
  1012. String(36), ForeignKey('share_types.id'), nullable=False)
  1013. share_group = orm.relationship(
  1014. ShareGroup,
  1015. backref="share_types",
  1016. foreign_keys=share_group_id,
  1017. primaryjoin=('and_('
  1018. 'ShareGroupShareTypeMapping.share_group_id '
  1019. '== ShareGroup.id,'
  1020. 'ShareGroupShareTypeMapping.deleted == "False")')
  1021. )
  1022. class Message(BASE, ManilaBase):
  1023. """Represents a user message.
  1024. User messages show information about API operations to the API end-user.
  1025. """
  1026. __tablename__ = 'messages'
  1027. id = Column(String(36), primary_key=True, nullable=False)
  1028. project_id = Column(String(255), nullable=False)
  1029. # Info/Error/Warning.
  1030. message_level = Column(String(255), nullable=False)
  1031. request_id = Column(String(255), nullable=True)
  1032. resource_type = Column(String(255))
  1033. # The uuid of the related resource.
  1034. resource_id = Column(String(36), nullable=True)
  1035. # Operation specific action ID, this ID is mapped
  1036. # to a message in manila/message/message_field.py
  1037. action_id = Column(String(10), nullable=False)
  1038. # After this time the message may no longer exist.
  1039. expires_at = Column(DateTime, nullable=True)
  1040. # Message detail ID, this ID is mapped
  1041. # to a message in manila/message/message_field.py
  1042. detail_id = Column(String(10), nullable=True)
  1043. deleted = Column(String(36), default='False')
  1044. class BackendInfo(BASE, ManilaBase):
  1045. """Represent Backend Info."""
  1046. __tablename__ = "backend_info"
  1047. host = Column(String(255), primary_key=True)
  1048. info_hash = Column(String(255))
  1049. def register_models():
  1050. """Register Models and create metadata.
  1051. Called from manila.db.sqlalchemy.__init__ as part of loading the driver,
  1052. it will never need to be called explicitly elsewhere unless the
  1053. connection is lost and needs to be reestablished.
  1054. """
  1055. from sqlalchemy import create_engine
  1056. models = (Service,
  1057. Share,
  1058. ShareAccessMapping,
  1059. ShareSnapshot
  1060. )
  1061. engine = create_engine(CONF.database.connection, echo=False)
  1062. for model in models:
  1063. model.metadata.create_all(engine)
  1064. def get_access_rules_status(instances):
  1065. share_access_status = constants.STATUS_ACTIVE
  1066. if len(instances) == 0:
  1067. return share_access_status
  1068. priorities = ShareInstance.ACCESS_STATUS_PRIORITIES
  1069. for instance in instances:
  1070. if instance['status'] != constants.STATUS_AVAILABLE:
  1071. continue
  1072. instance_access_status = instance['access_rules_status']
  1073. if priorities.get(instance_access_status) > priorities.get(
  1074. share_access_status):
  1075. share_access_status = instance_access_status
  1076. if share_access_status == constants.SHARE_INSTANCE_RULES_ERROR:
  1077. break
  1078. return share_access_status
  1079. def get_aggregated_access_rules_state(instance_mappings):
  1080. state = None
  1081. if len(instance_mappings) > 0:
  1082. order = (constants.ACCESS_STATE_ERROR,
  1083. constants.ACCESS_STATE_DENYING,
  1084. constants.ACCESS_STATE_QUEUED_TO_DENY,
  1085. constants.ACCESS_STATE_QUEUED_TO_APPLY,
  1086. constants.ACCESS_STATE_APPLYING,
  1087. constants.ACCESS_STATE_ACTIVE)
  1088. sorted_instance_mappings = sorted(
  1089. instance_mappings, key=lambda x: order.index(x['state']))
  1090. state = sorted_instance_mappings[0].state
  1091. return state