Browse Source

FIX-1237 register_myself in manager is resilient

Change-Id: I8f7623dd3b1717799efb8d7a378bb09af6efda12
changes/62/307062/1
Kanagaraj Manickam 3 years ago
parent
commit
0a765fe3ae

+ 1
- 1
etc/namos.conf View File

@@ -10,4 +10,4 @@ rabbit_hosts = 172.241.0.101
10 10
 connection = mysql+pymysql://root:password@172.241.0.101/namos?charset=utf8
11 11
 
12 12
 [conductor]
13
-workers=10
13
+workers=1

+ 2
- 1
namos/cmd/conductor.py View File

@@ -50,7 +50,8 @@ def main():
50 50
     launcher = os_service.launch(CONF, mgr, CONF.conductor.workers)
51 51
 
52 52
     # TODO(mrkanag) Namos is not registering the RPC backend, fix it !
53
-    # namos.register_myself()
53
+    import os_namos
54
+    os_namos.register_myself()
54 55
 
55 56
     launcher.wait()
56 57
 

+ 6
- 0
namos/common/exception.py View File

@@ -63,6 +63,12 @@ class NotFound(NamosException):
63 63
     http_status_code = 404
64 64
 
65 65
 
66
+class AlreadyExist(NamosException):
67
+    msg_fmt = ("%(model)s  %(name)s already exists")
68
+    error_code = 0x01002
69
+    http_status_code = 403
70
+
71
+
66 72
 class RegionNotFound(NotFound):
67 73
     msg_fmt = ("Region %(region_id)s does not found")
68 74
     error_code = 0x01001

+ 100
- 93
namos/conductor/manager.py View File

@@ -129,12 +129,6 @@ class ServiceProcessor(object):
129 129
     def process_service(self, context):
130 130
         # Service Node
131 131
         try:
132
-            # TODO(mrkanag) is this to be region specifc search
133
-            node = db_api.service_node_get_by_name(
134
-                context,
135
-                self.registration_info.get('fqdn'))
136
-            LOG.info('Service node %s is existing' % node)
137
-        except exception.ServiceNodeNotFound:
138 132
             # TODO(mrkanag) region_id is hard-coded, fix it !
139 133
             # user proper node name instead of fqdn
140 134
             node = db_api.service_node_create(
@@ -144,14 +138,15 @@ class ServiceProcessor(object):
144 138
                      region_id='f7dcd175-27ef-46b5-997f-e6e572f320b0'))
145 139
 
146 140
             LOG.info('Service node %s is created' % node)
141
+        except exception.AlreadyExist:
142
+            # TODO(mrkanag) is this to be region specifc search
143
+            node = db_api.service_node_get_by_name(
144
+                context,
145
+                self.registration_info.get('fqdn'))
146
+            LOG.info('Service node %s is existing' % node)
147 147
 
148 148
         # Service
149 149
         try:
150
-            service = db_api.service_get_by_name(
151
-                context,
152
-                self.registration_info.get('project_name'))
153
-            LOG.info('Service %s is existing' % service)
154
-        except exception.ServiceNotFound:
155 150
             s_id = 'b9c2549f-f685-4bc2-92e9-ba8af9c18591'
156 151
             service = db_api.service_create(
157 152
                 context,
@@ -161,53 +156,36 @@ class ServiceProcessor(object):
161 156
                      keystone_service_id=s_id))
162 157
 
163 158
             LOG.info('Service %s is created' % service)
159
+        except exception.AlreadyExist:
160
+            service = db_api.service_get_by_name(
161
+                context,
162
+                self.registration_info.get('project_name'))
163
+            LOG.info('Service %s is existing' % service)
164 164
 
165 165
         # Service Component
166
-        service_components = \
167
-            db_api.service_component_get_all_by_node_for_service(
168
-                context,
169
-                node_id=node.id,
170
-                service_id=service.id,
171
-                name=self.registration_info['prog_name']
172
-            )
173
-        if len(service_components) == 1:
174
-            service_component = service_components[0]
175
-            LOG.info('Service Component %s is existing' % service_component)
176
-        # TODO(mrkanag) what to do when service_components size is > 1
177
-        else:
166
+        try:
178 167
             service_component = db_api.service_component_create(
179 168
                 context,
180 169
                 dict(name=self.registration_info['prog_name'],
181 170
                      node_id=node.id,
182 171
                      service_id=service.id))
183 172
             LOG.info('Service Component %s is created' % service_component)
173
+        except exception.AlreadyExist:
174
+            service_components = \
175
+                db_api.service_component_get_all_by_node_for_service(
176
+                    context,
177
+                    node_id=node.id,
178
+                    service_id=service.id,
179
+                    name=self.registration_info['prog_name']
180
+                )
181
+            if len(service_components) == 1:
182
+                service_component = service_components[0]
183
+                LOG.info('Service Component %s is existing' %
184
+                         service_component)
185
+            # TODO(mrkanag) what to do when service_components size is > 1
184 186
 
185 187
         # Service Worker
186
-        # TODO(mrkanag) Find a way to purge the dead service worker
187
-        # Once each service  is enabled with heart beating namos
188
-        # purging can be done once heart beat stopped. this can be
189
-        # done from openstack.common.service.py
190
-        service_workers = \
191
-            db_api.service_worker_get_by_host_for_service_component(
192
-                context,
193
-                service_component_id=service_component.id,
194
-                host=self.registration_info['host']
195
-            )
196
-        if len(service_workers) == 1:
197
-            service_worker = \
198
-                db_api.service_worker_update(
199
-                    context,
200
-                    service_workers[0].id,
201
-                    dict(
202
-                        pid=self.registration_info['pid'],
203
-                        name='%s@%s' % (self.registration_info['pid'],
204
-                                        service_component.name)
205
-                    ))
206
-            LOG.info('Service Worker %s is existing and is updated'
207
-                     % service_worker)
208
-
209
-        # TODO(mrkanag) what to do when service_workers size is > 1
210
-        else:
188
+        try:
211 189
             service_worker = db_api.service_worker_create(
212 190
                 context,
213 191
                 # TODO(mrkanag) Fix the name, device driver proper !
@@ -217,6 +195,31 @@ class ServiceProcessor(object):
217 195
                      host=self.registration_info['host'],
218 196
                      service_component_id=service_component.id))
219 197
             LOG.info('Service Worker %s is created' % service_worker)
198
+        except exception.AlreadyExist:
199
+            # TODO(mrkanag) Find a way to purge the dead service worker
200
+            # Once each service  is enabled with heart beating namos
201
+            # purging can be done once heart beat stopped. this can be
202
+            # done from openstack.common.service.py
203
+            service_workers = \
204
+                db_api.service_worker_get_by_host_for_service_component(
205
+                    context,
206
+                    service_component_id=service_component.id,
207
+                    host=self.registration_info['host']
208
+                )
209
+            if len(service_workers) == 1:
210
+                service_worker = \
211
+                    db_api.service_worker_update(
212
+                        context,
213
+                        service_workers[0].id,
214
+                        dict(
215
+                            pid=self.registration_info['pid'],
216
+                            name='%s@%s' % (self.registration_info['pid'],
217
+                                            service_component.name)
218
+                        ))
219
+                LOG.info('Service Worker %s is existing and is updated'
220
+                         % service_worker)
221
+
222
+            # TODO(mrkanag) what to do when service_workers size is > 1
220 223
 
221 224
         # Config
222 225
         # TODO(mrkanag) Optimize the config like per service_component
@@ -224,18 +227,20 @@ class ServiceProcessor(object):
224 227
         for cfg_name, cfg_obj in self.registration_info[
225 228
             'config_dict'].iteritems():
226 229
             cfg_obj['service_worker_id'] = service_worker.id
227
-            configs = db_api.config_get_by_name_for_service_worker(
228
-                context,
229
-                service_worker_id=cfg_obj['service_worker_id'],
230
-                name=cfg_obj['name'])
231
-            if len(configs) == 1:
232
-                config = db_api.config_update(context,
233
-                                              configs[0].id,
234
-                                              cfg_obj)
235
-                LOG.info("Config %s is existing and is updated" % config)
236
-            else:
230
+
231
+            try:
237 232
                 config = db_api.config_create(context, cfg_obj)
238 233
                 LOG.info("Config %s is created" % config)
234
+            except exception.AlreadyExist:
235
+                configs = db_api.config_get_by_name_for_service_worker(
236
+                    context,
237
+                    service_worker_id=cfg_obj['service_worker_id'],
238
+                    name=cfg_obj['name'])
239
+                if len(configs) == 1:
240
+                    config = db_api.config_update(context,
241
+                                                  configs[0].id,
242
+                                                  cfg_obj)
243
+                    LOG.info("Config %s is existing and is updated" % config)
239 244
 
240 245
         return service_worker.id
241 246
 
@@ -367,11 +372,6 @@ class DriverProcessor(object):
367 372
             # Device
368 373
             device_name = self._get_value(device_cfg['name'])
369 374
             try:
370
-                device = db_api.device_get_by_name(
371
-                    context,
372
-                    device_name)
373
-                LOG.info('Device %s is existing' % device)
374
-            except exception.DeviceNotFound:
375 375
                 # TODO(mrkanag) region_id is hard-coded, fix it !
376 376
                 # Set the right status as well
377 377
                 device = db_api.device_create(
@@ -381,8 +381,13 @@ class DriverProcessor(object):
381 381
                          region_id='f7dcd175-27ef-46b5-997f-e6e572f320b0'))
382 382
 
383 383
                 LOG.info('Device %s is created' % device)
384
+            except exception.AlreadyExist:
385
+                device = db_api.device_get_by_name(
386
+                    context,
387
+                    device_name)
388
+                LOG.info('Device %s is existing' % device)
384 389
 
385
-            # Handle child devices
390
+            # TODO(mrkanag) Poperly Handle child devices
386 391
             if child_device_cfg is not None:
387 392
                 for d_name in self._get_value(child_device_cfg['key']):
388 393
                     base_name = self._get_value(child_device_cfg['base_name'])
@@ -406,16 +411,7 @@ class DriverProcessor(object):
406 411
                 LOG.info('Device %s is created' % device)
407 412
 
408 413
             # Device Endpoint
409
-            device_endpoints = db_api.device_endpoint_get_by_device_type(
410
-                context,
411
-                device_id=device.id,
412
-                type=endpoint_type,
413
-                name=device_endpoint_name)
414
-            if len(device_endpoints) >= 1:
415
-                device_endpoint = device_endpoints[0]
416
-                LOG.info('Device Endpoint %s is existing' %
417
-                         device_endpoints[0])
418
-            else:
414
+            try:
419 415
                 for k, v in connection_cfg.iteritems():
420 416
                     connection[k] = self._get_value(k)
421 417
 
@@ -426,15 +422,19 @@ class DriverProcessor(object):
426 422
                          type=endpoint_type,
427 423
                          device_id=device.id))
428 424
                 LOG.info('Device Endpoint %s is created' % device_endpoint)
425
+            except exception.AlreadyExist:
426
+                device_endpoints = db_api.device_endpoint_get_by_device_type(
427
+                    context,
428
+                    device_id=device.id,
429
+                    type=endpoint_type,
430
+                    name=device_endpoint_name)
431
+                if len(device_endpoints) >= 1:
432
+                    device_endpoint = device_endpoints[0]
433
+                    LOG.info('Device Endpoint %s is existing' %
434
+                             device_endpoints[0])
429 435
 
430 436
             # Device Driver Class
431 437
             try:
432
-                device_driver_class = db_api.device_driver_class_get_by_name(
433
-                    context,
434
-                    driver_name)
435
-                LOG.info('Device Driver Class %s is existing' %
436
-                         device_driver_class)
437
-            except exception.DeviceDriverClassNotFound:
438 438
                 device_driver_class = db_api.device_driver_class_create(
439 439
                     context,
440 440
                     dict(name=driver_name,
@@ -446,21 +446,15 @@ class DriverProcessor(object):
446 446
                          extra=driver_def.get('extra')))
447 447
                 LOG.info('Device Driver Class %s is created' %
448 448
                          device_driver_class)
449
+            except exception.AlreadyExist:
450
+                device_driver_class = db_api.device_driver_class_get_by_name(
451
+                    context,
452
+                    driver_name)
453
+                LOG.info('Device Driver Class %s is existing' %
454
+                         device_driver_class)
449 455
 
450 456
             # Device Driver
451
-            device_drivers = \
452
-                db_api.device_driver_get_by_device_endpoint_service_worker(
453
-                    context,
454
-                    device_id=device.id,
455
-                    endpoint_id=device_endpoint.id,
456
-                    device_driver_class_id=device_driver_class.id,
457
-                    service_worker_id=self.service_worker_id
458
-                )
459
-            if len(device_drivers) >= 1:
460
-                device_driver = device_drivers[0]
461
-                LOG.info('Device Driver %s is existing' %
462
-                         device_driver)
463
-            else:
457
+            try:
464 458
                 device_driver = db_api.device_driver_create(
465 459
                     context,
466 460
                     dict(device_id=device.id,
@@ -471,6 +465,19 @@ class DriverProcessor(object):
471 465
                 )
472 466
                 LOG.info('Device Driver %s is created' %
473 467
                          device_driver)
468
+            except exception.AlreadyExist:
469
+                device_drivers = \
470
+                    db_api.device_driver_get_by_device_endpoint_service_worker(
471
+                        context,
472
+                        device_id=device.id,
473
+                        endpoint_id=device_endpoint.id,
474
+                        device_driver_class_id=device_driver_class.id,
475
+                        service_worker_id=self.service_worker_id
476
+                    )
477
+                if len(device_drivers) >= 1:
478
+                    device_driver = device_drivers[0]
479
+                    LOG.info('Device Driver %s is existing' %
480
+                             device_driver)
474 481
 
475 482
 
476 483
 if __name__ == '__main__':

+ 10
- 9
namos/db/sqlalchemy/alembic/versions/48ebec3cd6f6_initial_version.py View File

@@ -34,7 +34,7 @@ def upgrade():
34 34
         sa.Column('created_at', sa.DateTime(), nullable=True),
35 35
         sa.Column('updated_at', sa.DateTime(), nullable=True),
36 36
         sa.Column('id', sa.Uuid(length=36), nullable=False),
37
-        sa.Column('name', sa.String(length=255), nullable=False),
37
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
38 38
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
39 39
         sa.Column('extra', sa.Json(), nullable=True),
40 40
         sa.Column('keystone_service_id', sa.Uuid(length=36), nullable=False),
@@ -47,7 +47,7 @@ def upgrade():
47 47
         sa.Column('created_at', sa.DateTime(), nullable=True),
48 48
         sa.Column('updated_at', sa.DateTime(), nullable=True),
49 49
         sa.Column('id', sa.Uuid(length=36), nullable=False),
50
-        sa.Column('name', sa.String(length=255), nullable=False),
50
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
51 51
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
52 52
         sa.Column('extra', sa.Json(), nullable=True),
53 53
         sa.Column('python_class', sa.String(length=256), nullable=False),
@@ -62,7 +62,7 @@ def upgrade():
62 62
         sa.Column('created_at', sa.DateTime(), nullable=True),
63 63
         sa.Column('updated_at', sa.DateTime(), nullable=True),
64 64
         sa.Column('id', sa.Uuid(length=36), nullable=False),
65
-        sa.Column('name', sa.String(length=255), nullable=False),
65
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
66 66
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
67 67
         sa.Column('extra', sa.Json(), nullable=True),
68 68
         sa.Column('keystone_region_id', sa.String(length=255), nullable=False),
@@ -75,7 +75,7 @@ def upgrade():
75 75
         sa.Column('created_at', sa.DateTime(), nullable=True),
76 76
         sa.Column('updated_at', sa.DateTime(), nullable=True),
77 77
         sa.Column('id', sa.Uuid(length=36), nullable=False),
78
-        sa.Column('name', sa.String(length=255), nullable=False),
78
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
79 79
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
80 80
         sa.Column('status', sa.String(length=64), nullable=False),
81 81
         sa.Column('description', sa.Text(), nullable=True),
@@ -94,7 +94,7 @@ def upgrade():
94 94
         sa.Column('created_at', sa.DateTime(), nullable=True),
95 95
         sa.Column('updated_at', sa.DateTime(), nullable=True),
96 96
         sa.Column('id', sa.Uuid(length=36), nullable=False),
97
-        sa.Column('name', sa.String(length=255), nullable=False),
97
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
98 98
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
99 99
         sa.Column('description', sa.Text(), nullable=True),
100 100
         sa.Column('extra', sa.Json(), nullable=True),
@@ -110,7 +110,7 @@ def upgrade():
110 110
         sa.Column('created_at', sa.DateTime(), nullable=True),
111 111
         sa.Column('updated_at', sa.DateTime(), nullable=True),
112 112
         sa.Column('id', sa.Uuid(length=36), nullable=False),
113
-        sa.Column('name', sa.String(length=255), nullable=False),
113
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
114 114
         sa.Column('extra', sa.Json(), nullable=True),
115 115
         sa.Column('device_id', sa.Uuid(length=36), nullable=True),
116 116
         sa.Column('connection', sa.Json(), nullable=False),
@@ -124,7 +124,7 @@ def upgrade():
124 124
         sa.Column('created_at', sa.DateTime(), nullable=True),
125 125
         sa.Column('updated_at', sa.DateTime(), nullable=True),
126 126
         sa.Column('id', sa.Uuid(length=36), nullable=False),
127
-        sa.Column('name', sa.String(length=255), nullable=False),
127
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
128 128
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
129 129
         sa.Column('description', sa.Text(), nullable=True),
130 130
         sa.Column('extra', sa.Json(), nullable=True),
@@ -141,7 +141,7 @@ def upgrade():
141 141
         sa.Column('created_at', sa.DateTime(), nullable=True),
142 142
         sa.Column('updated_at', sa.DateTime(), nullable=True),
143 143
         sa.Column('id', sa.Uuid(length=36), nullable=False),
144
-        sa.Column('name', sa.String(length=255), nullable=False),
144
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
145 145
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
146 146
         sa.Column('extra', sa.Json(), nullable=True),
147 147
         sa.Column('endpoint_id', sa.Uuid(length=36), nullable=True),
@@ -160,7 +160,7 @@ def upgrade():
160 160
         sa.Column('created_at', sa.DateTime(), nullable=True),
161 161
         sa.Column('updated_at', sa.DateTime(), nullable=True),
162 162
         sa.Column('id', sa.Uuid(length=36), nullable=False),
163
-        sa.Column('name', sa.String(length=255), nullable=False),
163
+        sa.Column('name', sa.String(length=255), nullable=False, unique=True),
164 164
         sa.Column('deleted_at', sa.DateTime(), nullable=True),
165 165
         sa.Column('extra', sa.Json(), nullable=True),
166 166
         sa.Column('pid', sa.String(length=32), nullable=False),
@@ -173,6 +173,7 @@ def upgrade():
173 173
         sa.PrimaryKeyConstraint('id'),
174 174
         mysql_engine='InnoDB'
175 175
         )
176
+# TODO(mrkanag) add oslo_config schema here
176 177
 
177 178
 
178 179
 def downgrade():

+ 6
- 1
namos/db/sqlalchemy/api.py View File

@@ -16,6 +16,7 @@
16 16
 import sys
17 17
 
18 18
 from oslo_config import cfg
19
+from oslo_db import exception as db_exception
19 20
 from oslo_db.sqlalchemy import session as db_session
20 21
 
21 22
 from namos.common import exception
@@ -57,7 +58,11 @@ def _session(context):
57 58
 
58 59
 def _create(context, resource_ref, values):
59 60
     resource_ref.update(values)
60
-    resource_ref.save(_session(context))
61
+    try:
62
+        resource_ref.save(_session(context))
63
+    except db_exception.DBDuplicateEntry:
64
+        raise exception.AlreadyExist(model=resource_ref.__class__.__name__,
65
+                                     name=resource_ref.name)
61 66
     return resource_ref
62 67
 
63 68
 

+ 40
- 2
namos/db/sqlalchemy/models.py View File

@@ -18,6 +18,7 @@ SQLAlchemy models for namos database
18 18
 
19 19
 import sqlalchemy
20 20
 from sqlalchemy.ext.declarative import declarative_base
21
+from sqlalchemy import UniqueConstraint
21 22
 import uuid
22 23
 
23 24
 from namos.db.sqlalchemy.types import Json
@@ -37,7 +38,7 @@ class NamosBase(models.ModelBase,
37 38
     id = sqlalchemy.Column(Uuid, primary_key=True,
38 39
                            default=lambda: str(uuid.uuid4()))
39 40
     name = sqlalchemy.Column(sqlalchemy.String(255),
40
-                             # unique=True,
41
+                             unique=True,
41 42
                              nullable=False,
42 43
                              default=lambda: str(uuid.uuid4()))
43 44
 
@@ -128,6 +129,9 @@ class DeviceEndpoint(BASE,
128 129
                      Extra):
129 130
     __tablename__ = 'device_endpoint'
130 131
 
132
+    __table_args__ = (
133
+        UniqueConstraint("device_id", "type"),
134
+    )
131 135
     device_id = sqlalchemy.Column(
132 136
         Uuid,
133 137
         sqlalchemy.ForeignKey('device.id'),
@@ -145,6 +149,12 @@ class DeviceDriver(BASE,
145 149
                    SoftDelete,
146 150
                    Extra):
147 151
     __tablename__ = 'device_driver'
152
+    __table_args__ = (
153
+        UniqueConstraint("device_id",
154
+                         "endpoint_id",
155
+                         "device_driver_class_id",
156
+                         "service_worker_id"),
157
+    )
148 158
 
149 159
     endpoint_id = sqlalchemy.Column(
150 160
         Uuid,
@@ -179,7 +189,8 @@ class DeviceDriverClass(BASE,
179 189
     # TODO(kanagaraj-manickam) Correct the max python class path here
180 190
     python_class = sqlalchemy.Column(
181 191
         sqlalchemy.String(256),
182
-        nullable=False
192
+        nullable=False,
193
+        unique=True
183 194
     )
184 195
     # service type like compute, network, volume, etc
185 196
     type = sqlalchemy.Column(
@@ -225,6 +236,15 @@ class ServiceComponent(BASE,
225 236
                        Extra):
226 237
     __tablename__ = 'service_component'
227 238
 
239
+    __table_args__ = (
240
+        UniqueConstraint("name", "node_id", "service_id"),
241
+    )
242
+
243
+    name = sqlalchemy.Column(sqlalchemy.String(255),
244
+                             # unique=True,
245
+                             nullable=False,
246
+                             default=lambda: str(uuid.uuid4()))
247
+
228 248
     node_id = sqlalchemy.Column(
229 249
         Uuid,
230 250
         sqlalchemy.ForeignKey('service_node.id'),
@@ -241,6 +261,15 @@ class ServiceWorker(BASE,
241 261
                     Extra):
242 262
     __tablename__ = 'service_worker'
243 263
 
264
+    __table_args__ = (
265
+        UniqueConstraint("host", "service_component_id"),
266
+    )
267
+
268
+    name = sqlalchemy.Column(sqlalchemy.String(255),
269
+                             # unique=True,
270
+                             nullable=False,
271
+                             default=lambda: str(uuid.uuid4()))
272
+
244 273
     pid = sqlalchemy.Column(
245 274
         sqlalchemy.String(32),
246 275
         nullable=False
@@ -261,9 +290,18 @@ class OsloConfig(BASE,
261 290
                  Extra):
262 291
     __tablename__ = 'oslo_config'
263 292
 
293
+    __table_args__ = (
294
+        UniqueConstraint("name", "service_worker_id"),
295
+    )
296
+
264 297
     default_value = sqlalchemy.Column(
265 298
         sqlalchemy.Text
266 299
     )
300
+    name = sqlalchemy.Column(sqlalchemy.String(255),
301
+                             # unique=True,
302
+                             nullable=False,
303
+                             default=lambda: str(uuid.uuid4()))
304
+
267 305
     help = sqlalchemy.Column(
268 306
         sqlalchemy.Text,
269 307
         nullable=False,

Loading…
Cancel
Save