diff --git a/mogan/api/controllers/v1/servers.py b/mogan/api/controllers/v1/servers.py index 0d015bd4..246bb589 100644 --- a/mogan/api/controllers/v1/servers.py +++ b/mogan/api/controllers/v1/servers.py @@ -46,7 +46,8 @@ import re _DEFAULT_SERVER_RETURN_FIELDS = ('uuid', 'name', 'description', 'status', 'power_state') -_ONLY_ADMIN_VISIBLE_SEVER_FIELDS = ('node', 'affinity_zone',) +_ONLY_ADMIN_VISIBLE_SERVER_FIELDS = ('node', 'affinity_zone', + 'system_metadata') LOG = log.getLogger(__name__) @@ -433,6 +434,9 @@ class Server(base.APIBase): locked = types.boolean """Represent the current lock state of the server""" + system_metadata = {wtypes.text: types.jsontype} + """The system meta data of the server""" + def __init__(self, **kwargs): super(Server, self).__init__(**kwargs) self.fields = [] @@ -445,7 +449,7 @@ class Server(base.APIBase): if kwargs.get('status') != 'error': setattr(self, field, wtypes.Unset) continue - if field in _ONLY_ADMIN_VISIBLE_SEVER_FIELDS: + if field in _ONLY_ADMIN_VISIBLE_SERVER_FIELDS: if not pecan.request.context.is_admin: setattr(self, field, wtypes.Unset) continue @@ -487,7 +491,8 @@ class ServerPatchType(types.JsonPatchType): '/power_state', '/availability_zone', '/flavor_uuid', '/image_uuid', '/addresses', '/launched_at', '/affinity_zone', '/key_name', - '/partitions', '/fault', '/node', '/locked'] + '/partitions', '/fault', '/node', '/locked', + '/system_metadata'] class ServerCollection(base.APIBase): diff --git a/mogan/db/sqlalchemy/alembic/versions/91941bf1ebc9_initial_migration.py b/mogan/db/sqlalchemy/alembic/versions/91941bf1ebc9_initial_migration.py index f5d0b22d..c56276bb 100644 --- a/mogan/db/sqlalchemy/alembic/versions/91941bf1ebc9_initial_migration.py +++ b/mogan/db/sqlalchemy/alembic/versions/91941bf1ebc9_initial_migration.py @@ -85,6 +85,7 @@ def upgrade(): sa.Column('affinity_zone', sa.String(length=255), nullable=True), sa.Column('locked_by', sa.Enum('admin', 'owner'), nullable=True), sa.Column('key_name', sa.String(length=255), nullable=True), + sa.Column('system_metadata', sa.Text(), nullable=True), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('uuid', name='uniq_servers0uuid'), sa.Index('servers_project_id_idx', 'project_id'), diff --git a/mogan/db/sqlalchemy/models.py b/mogan/db/sqlalchemy/models.py index f70ab729..7eb11457 100644 --- a/mogan/db/sqlalchemy/models.py +++ b/mogan/db/sqlalchemy/models.py @@ -93,6 +93,7 @@ class Server(Base): locked_by = Column(Enum('owner', 'admin')) affinity_zone = Column(String(255), nullable=True) key_name = Column(String(255), nullable=True) + system_metadata = Column(db_types.JsonEncodedDict, nullable=True) class ServerNic(Base): diff --git a/mogan/engine/manager.py b/mogan/engine/manager.py index 047f5b67..884a440c 100644 --- a/mogan/engine/manager.py +++ b/mogan/engine/manager.py @@ -785,6 +785,7 @@ class EngineManager(base_manager.BaseEngineManager): server.power_state = node['power_state'] server.launched_at = timeutils.utcnow() server.status = states.ACTIVE + server.system_metadata = {"managed_server": "True"} if server.power_state == states.POWER_OFF: server.status = states.STOPPED diff --git a/mogan/objects/server.py b/mogan/objects/server.py index 9a801c75..700ae6c0 100644 --- a/mogan/objects/server.py +++ b/mogan/objects/server.py @@ -57,6 +57,7 @@ class Server(base.MoganObject, object_base.VersionedObjectDictCompat): 'locked_by': object_fields.StringField(nullable=True), 'affinity_zone': object_fields.StringField(nullable=True), 'key_name': object_fields.StringField(nullable=True), + 'system_metadata': object_fields.FlexibleDictField(nullable=True), } def __init__(self, context=None, **kwargs): diff --git a/mogan/tests/unit/api/v1/test_server.py b/mogan/tests/unit/api/v1/test_server.py index 0e7be9f5..6e1d305b 100644 --- a/mogan/tests/unit/api/v1/test_server.py +++ b/mogan/tests/unit/api/v1/test_server.py @@ -185,6 +185,23 @@ class TestServerAuthorization(v1_test.APITestV1): headers = self.gen_headers(self.context) self.post_json('/servers', body, headers=headers, status=403) + def test_server_get_one_by_owner_without_system_metadata(self): + # not admin but the owner + self.context.tenant = self.server1.project_id + headers = self.gen_headers(self.context, roles="no-admin") + resp = self.get_json('/servers/%s' % self.server1.uuid, + headers=headers) + self.assertNotIn('system_metadata', resp) + + def test_server_get_one_by_admin_without_system_metadata(self): + # when the evil tenant is admin, he can do everything. + self.context.tenant = self.evil_project + headers = self.gen_headers(self.context, roles="admin") + resp = self.get_json('/servers/%s' % self.server1.uuid, + headers=headers) + self.assertIn('system_metadata', resp) + self.assertEqual(resp['system_metadata'], {}) + class TestPatch(v1_test.APITestV1): diff --git a/mogan/tests/unit/db/utils.py b/mogan/tests/unit/db/utils.py index c50158c2..f505134d 100644 --- a/mogan/tests/unit/db/utils.py +++ b/mogan/tests/unit/db/utils.py @@ -66,7 +66,8 @@ def get_test_server(**kw): 'created_at': kw.get('created_at'), 'locked_by': kw.get('locked_by', None), 'affinity_zone': kw.get('affinity_zone', 'ZON1'), - 'key_name': kw.get('key_name', 'test_key') + 'key_name': kw.get('key_name', 'test_key'), + 'system_metadata': kw.get('system_metadata', {}) } diff --git a/mogan/tests/unit/objects/test_objects.py b/mogan/tests/unit/objects/test_objects.py index a1060393..9c0ecaa7 100644 --- a/mogan/tests/unit/objects/test_objects.py +++ b/mogan/tests/unit/objects/test_objects.py @@ -382,7 +382,7 @@ class _TestObject(object): # version bump. It is md5 hash of object fields and remotable methods. # The fingerprint values should only be changed if there is a version bump. expected_object_fingerprints = { - 'Server': '1.0-1480ee88a244bf44492f61f20a022a6f', + 'Server': '1.0-3e78c39d5ddb7b08ecdb7dad3a579b49', 'ServerFault': '1.0-74349ff701259e4834b4e9dc2dac1b12', 'ServerFaultList': '1.0-43e8aad0258652921f929934e9e048fd', 'Flavor': '1.0-9f7166aa387d89ec40cd699019d0c9a9',