IGNITE-15007 Add BASELINE_NODE_ATTRIBUTES system view (#9204)

diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
index ac5b077..d381d1b 100755
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
@@ -321,6 +321,7 @@
     public void testGetAllView() throws Exception {
         Set<String> expViews = new HashSet<>(Arrays.asList(
             "BASELINE_NODES",
+            "BASELINE_NODE_ATTRIBUTES",
             "CACHES",
             "CACHE_GROUPS",
             "INDEXES",
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
index 7694d69..cea2d07 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
@@ -425,6 +425,7 @@
                 "SYS.SCHEMAS",
                 "SYS.NODE_METRICS",
                 "SYS.BASELINE_NODES",
+                "SYS.BASELINE_NODE_ATTRIBUTES",
                 "SYS.INDEXES",
                 "SYS.LOCAL_CACHE_GROUPS_IO",
                 "SYS.SQL_QUERIES",
@@ -656,6 +657,9 @@
             expectedCols = new HashSet<>(Arrays.asList(
                 "SYS.BASELINE_NODES.CONSISTENT_ID.null.2147483647",
                 "SYS.BASELINE_NODES.ONLINE.null.1",
+                "SYS.BASELINE_NODE_ATTRIBUTES.NODE_CONSISTENT_ID.null.2147483647",
+                "SYS.BASELINE_NODE_ATTRIBUTES.NAME.null.2147483647",
+                "SYS.BASELINE_NODE_ATTRIBUTES.VALUE.null.2147483647",
                 "SYS.CACHES.CACHE_GROUP_ID.null.10",
                 "SYS.CACHES.CACHE_GROUP_NAME.null.2147483647",
                 "SYS.CACHES.CACHE_ID.null.10",
diff --git a/modules/codegen/src/main/java/org/apache/ignite/codegen/SystemViewRowAttributeWalkerGenerator.java b/modules/codegen/src/main/java/org/apache/ignite/codegen/SystemViewRowAttributeWalkerGenerator.java
index 2ad5eb7..d48f1c8 100644
--- a/modules/codegen/src/main/java/org/apache/ignite/codegen/SystemViewRowAttributeWalkerGenerator.java
+++ b/modules/codegen/src/main/java/org/apache/ignite/codegen/SystemViewRowAttributeWalkerGenerator.java
@@ -39,6 +39,7 @@
 import org.apache.ignite.internal.processors.query.stat.view.StatisticsColumnLocalDataView;
 import org.apache.ignite.internal.processors.query.stat.view.StatisticsColumnPartitionDataView;
 import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.spi.systemview.view.BaselineNodeAttributeView;
 import org.apache.ignite.spi.systemview.view.BinaryMetadataView;
 import org.apache.ignite.spi.systemview.view.CacheGroupView;
 import org.apache.ignite.spi.systemview.view.CachePagesListView;
@@ -129,6 +130,7 @@
         gen.generateAndWrite(CountDownLatchView.class, DFLT_SRC_DIR);
         gen.generateAndWrite(ReentrantLockView.class, DFLT_SRC_DIR);
         gen.generateAndWrite(SemaphoreView.class, DFLT_SRC_DIR);
+        gen.generateAndWrite(BaselineNodeAttributeView.class, DFLT_SRC_DIR);
 
         gen.generateAndWrite(SqlSchemaView.class, INDEXING_SRC_DIR);
         gen.generateAndWrite(SqlTableView.class, INDEXING_SRC_DIR);
diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java
index e4c21e2..d1f5e6e 100644
--- a/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java
+++ b/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java
@@ -426,6 +426,7 @@
             "SCHEMAS",
             "NODE_METRICS",
             "BASELINE_NODES",
+            "BASELINE_NODE_ATTRIBUTES",
             "INDEXES",
             "LOCAL_CACHE_GROUPS_IO",
             "SQL_QUERIES",
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/BaselineNodeAttributeViewWalker.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/BaselineNodeAttributeViewWalker.java
new file mode 100644
index 0000000..d4996de
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/BaselineNodeAttributeViewWalker.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.managers.systemview.walker;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.spi.systemview.view.BaselineNodeAttributeView;
+import org.apache.ignite.spi.systemview.view.SystemViewRowAttributeWalker;
+
+/**
+ * Generated by {@code org.apache.ignite.codegen.SystemViewRowAttributeWalkerGenerator}.
+ * {@link BaselineNodeAttributeView} attributes walker.
+ * 
+ * @see BaselineNodeAttributeView
+ */
+public class BaselineNodeAttributeViewWalker implements SystemViewRowAttributeWalker<BaselineNodeAttributeView> {
+    /** Filter key for attribute "nodeConsistentId" */
+    public static final String NODE_CONSISTENT_ID_FILTER = "nodeConsistentId";
+
+    /** Filter key for attribute "name" */
+    public static final String NAME_FILTER = "name";
+
+    /** List of filtrable attributes. */
+    private static final List<String> FILTRABLE_ATTRS = Collections.unmodifiableList(F.asList(
+        "nodeConsistentId", "name"
+    ));
+
+    /** {@inheritDoc} */
+    @Override public List<String> filtrableAttributes() {
+        return FILTRABLE_ATTRS;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void visitAll(AttributeVisitor v) {
+        v.accept(0, "nodeConsistentId", String.class);
+        v.accept(1, "name", String.class);
+        v.accept(2, "value", String.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void visitAll(BaselineNodeAttributeView row, AttributeWithValueVisitor v) {
+        v.accept(0, "nodeConsistentId", String.class, row.nodeConsistentId());
+        v.accept(1, "name", String.class, row.name());
+        v.accept(2, "value", String.class, row.value());
+    }
+
+    /** {@inheritDoc} */
+    @Override public int count() {
+        return 3;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/SqlQueryViewWalker.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/SqlQueryViewWalker.java
index 83e6425..1bfa4d9 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/SqlQueryViewWalker.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/systemview/walker/SqlQueryViewWalker.java
@@ -36,9 +36,9 @@
         v.accept(2, "originNodeId", UUID.class);
         v.accept(3, "startTime", Date.class);
         v.accept(4, "duration", long.class);
-        v.accept(5, "local", boolean.class);
-        v.accept(6, "schemaName", String.class);
-        v.accept(7, "initiatorId", String.class);
+        v.accept(5, "initiatorId", String.class);
+        v.accept(6, "local", boolean.class);
+        v.accept(7, "schemaName", String.class);
     }
 
     /** {@inheritDoc} */
@@ -48,9 +48,9 @@
         v.accept(2, "originNodeId", UUID.class, row.originNodeId());
         v.accept(3, "startTime", Date.class, row.startTime());
         v.acceptLong(4, "duration", row.duration());
-        v.acceptBoolean(5, "local", row.local());
-        v.accept(6, "schemaName", String.class, row.schemaName());
-        v.accept(7, "initiatorId", String.class, row.initiatorId());
+        v.accept(5, "initiatorId", String.class, row.initiatorId());
+        v.acceptBoolean(6, "local", row.local());
+        v.accept(7, "schemaName", String.class, row.schemaName());
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java
index b839a6c..f99555f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java
@@ -21,6 +21,7 @@
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -30,6 +31,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteCompute;
@@ -56,6 +58,7 @@
 import org.apache.ignite.internal.managers.discovery.DiscoCache;
 import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpi;
 import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener;
+import org.apache.ignite.internal.managers.systemview.walker.BaselineNodeAttributeViewWalker;
 import org.apache.ignite.internal.processors.GridProcessorAdapter;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.ExchangeActions;
@@ -92,6 +95,7 @@
 import org.apache.ignite.marshaller.jdk.JdkMarshaller;
 import org.apache.ignite.spi.IgniteNodeValidationResult;
 import org.apache.ignite.spi.discovery.DiscoveryDataBag;
+import org.apache.ignite.spi.systemview.view.BaselineNodeAttributeView;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.cluster.ClusterState.ACTIVE;
@@ -110,6 +114,8 @@
 import static org.apache.ignite.internal.managers.communication.GridIoPolicy.SYSTEM_POOL;
 import static org.apache.ignite.internal.processors.cache.GridCacheUtils.extractDataStorage;
 import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isPersistentCache;
+import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName;
+import static org.apache.ignite.internal.util.IgniteUtils.toStringSafe;
 
 /**
  *
@@ -123,6 +129,12 @@
         "in-memory caches (without persistence) including the system caches.";
 
     /** */
+    public static final String BASELINE_NODE_ATTRIBUTES_SYS_VIEW = metricName("baseline", "node", "attributes");
+
+    /** */
+    public static final String BASELINE_NODE_ATTRIBUTES_SYS_VIEW_DESC = "Baseline node attributes";
+
+    /** */
     private boolean inMemoryMode;
 
     /**
@@ -212,6 +224,14 @@
         distributedBaselineConfiguration.listenAutoAdjustTimeout(makeEventListener(
             EVT_BASELINE_AUTO_ADJUST_AWAITING_TIME_CHANGED
         ));
+
+        ctx.systemView().registerFiltrableView(
+            BASELINE_NODE_ATTRIBUTES_SYS_VIEW,
+            BASELINE_NODE_ATTRIBUTES_SYS_VIEW_DESC,
+            new BaselineNodeAttributeViewWalker(),
+            this::nodeAttributeViewSupplier,
+            Function.identity()
+        );
     }
 
     /** */
@@ -1838,6 +1858,41 @@
     }
 
     /**
+     * Baseline node attributes view supplier.
+     *
+     * @param filter Filter.
+     */
+    private Iterable<BaselineNodeAttributeView> nodeAttributeViewSupplier(Map<String, Object> filter) {
+        String nodeConsistentId = (String)filter.get(BaselineNodeAttributeViewWalker.NODE_CONSISTENT_ID_FILTER);
+        String attrName = (String)filter.get(BaselineNodeAttributeViewWalker.NAME_FILTER);
+
+        BaselineTopology blt = globalState.baselineTopology();
+
+        if (blt == null)
+            return Collections.emptyList();
+
+        return F.flat(F.iterator(blt.currentBaseline(), node -> {
+            Map<String, Object> attrs = node.attributes();
+
+            if (nodeConsistentId != null && !nodeConsistentId.equals(toStringSafe(node.consistentId())))
+                return Collections.emptyList();
+
+            if (attrName != null) {
+                Object attrVal = attrs.get(attrName);
+
+                if (attrVal == null)
+                    return Collections.emptyList();
+
+                attrs = F.asMap(attrName, attrs.get(attrName));
+            }
+
+            return F.iterator(attrs.entrySet(),
+                na -> new BaselineNodeAttributeView(node.consistentId(), na.getKey(), na.getValue()),
+                true);
+        }, true));
+    }
+
+    /**
      * @return {@code True} if cluster has in-memory caches (without persistence) including the system caches.
      * {@code False} otherwise.
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/BaselineNodeAttributeView.java b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/BaselineNodeAttributeView.java
new file mode 100644
index 0000000..cd76079
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/BaselineNodeAttributeView.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.spi.systemview.view;
+
+import org.apache.ignite.internal.managers.systemview.walker.Filtrable;
+import org.apache.ignite.internal.managers.systemview.walker.Order;
+
+import static org.apache.ignite.internal.util.IgniteUtils.toStringSafe;
+
+/**
+ * Baseline node attribute representation for a {@link SystemView}.
+ */
+public class BaselineNodeAttributeView {
+    /** Node consistent id. */
+    private final Object consistentId;
+
+    /** Attribute name. */
+    private final String name;
+
+    /** Attribute value. */
+    private final Object val;
+
+    /**
+     * @param consistentId Node consistent id.
+     * @param name Attribute name.
+     * @param val Attribute value.
+     */
+    public BaselineNodeAttributeView(Object consistentId, String name, Object val) {
+        this.consistentId = consistentId;
+        this.name = name;
+        this.val = val;
+    }
+
+    /**
+     * @return Node consistend id.
+     */
+    @Order
+    @Filtrable
+    public String nodeConsistentId() {
+        return toStringSafe(consistentId);
+    }
+
+    /**
+     * @return Attribute name.
+     */
+    @Order(1)
+    @Filtrable
+    public String name() {
+        return name;
+    }
+
+    /**
+     * @return Attribute value.
+     */
+    @Order(2)
+    public String value() {
+        return toStringSafe(val);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewSelfTest.java
index 4d05258..95240e2 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewSelfTest.java
@@ -73,6 +73,7 @@
 import org.apache.ignite.internal.binary.mutabletest.GridBinaryTestClasses.TestObjectAllTypes;
 import org.apache.ignite.internal.binary.mutabletest.GridBinaryTestClasses.TestObjectEnum;
 import org.apache.ignite.internal.client.thin.ProtocolVersion;
+import org.apache.ignite.internal.managers.systemview.walker.BaselineNodeAttributeViewWalker;
 import org.apache.ignite.internal.managers.systemview.walker.CachePagesListViewWalker;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
@@ -89,6 +90,7 @@
 import org.apache.ignite.lang.IgnitePredicate;
 import org.apache.ignite.lang.IgniteRunnable;
 import org.apache.ignite.services.ServiceConfiguration;
+import org.apache.ignite.spi.systemview.view.BaselineNodeAttributeView;
 import org.apache.ignite.spi.systemview.view.BinaryMetadataView;
 import org.apache.ignite.spi.systemview.view.CacheGroupView;
 import org.apache.ignite.spi.systemview.view.CachePagesListView;
@@ -135,6 +137,7 @@
 import static org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager.METASTORE_VIEW;
 import static org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager.DATA_REGION_PAGE_LIST_VIEW;
 import static org.apache.ignite.internal.processors.cache.transactions.IgniteTxManager.TXS_MON_LIST;
+import static org.apache.ignite.internal.processors.cluster.GridClusterStateProcessor.BASELINE_NODE_ATTRIBUTES_SYS_VIEW;
 import static org.apache.ignite.internal.processors.continuous.GridContinuousProcessor.CQ_SYS_VIEW;
 import static org.apache.ignite.internal.processors.datastructures.DataStructuresProcessor.DEFAULT_DS_GROUP_NAME;
 import static org.apache.ignite.internal.processors.datastructures.DataStructuresProcessor.DEFAULT_VOLATILE_DS_GROUP_NAME;
@@ -1866,6 +1869,50 @@
         }
     }
 
+    /** */
+    @Test
+    public void testBaselineNodeAttributes() throws Exception {
+        cleanPersistenceDir();
+
+        try (IgniteEx ignite = startGrid(getConfiguration()
+            .setDataStorageConfiguration(
+                new DataStorageConfiguration().setDefaultDataRegionConfiguration(
+                    new DataRegionConfiguration().setName("pds").setPersistenceEnabled(true)
+                ))
+            .setUserAttributes(F.asMap("name", "val"))
+            .setConsistentId("consId"))
+        ) {
+            ignite.cluster().state(ClusterState.ACTIVE);
+
+            SystemView<BaselineNodeAttributeView> view = ignite.context().systemView()
+                .view(BASELINE_NODE_ATTRIBUTES_SYS_VIEW);
+
+            assertEquals(ignite.cluster().localNode().attributes().size(), view.size());
+
+            assertEquals(1, F.size(view.iterator(), row -> "consId".equals(row.nodeConsistentId()) &&
+                "name".equals(row.name()) && "val".equals(row.value())));
+
+            // Test filtering.
+            assertTrue(view instanceof FiltrableSystemView);
+
+            Iterator<BaselineNodeAttributeView> iter = ((FiltrableSystemView<BaselineNodeAttributeView>)view)
+                .iterator(F.asMap(BaselineNodeAttributeViewWalker.NODE_CONSISTENT_ID_FILTER, "consId",
+                    BaselineNodeAttributeViewWalker.NAME_FILTER, "name"));
+
+            assertEquals(1, F.size(iter));
+
+            iter = ((FiltrableSystemView<BaselineNodeAttributeView>)view).iterator(
+                F.asMap(BaselineNodeAttributeViewWalker.NODE_CONSISTENT_ID_FILTER, "consId"));
+
+            assertEquals(1, F.size(iter, row -> "name".equals(row.name())));
+
+            iter = ((FiltrableSystemView<BaselineNodeAttributeView>)view).iterator(
+                F.asMap(BaselineNodeAttributeViewWalker.NAME_FILTER, "name"));
+
+            assertEquals(1, F.size(iter));
+        }
+    }
+
     /** Test node filter. */
     public static class TestNodeFilter implements IgnitePredicate<ClusterNode> {
         /** {@inheritDoc} */
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java
index f9f0673..c5f2ff0 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java
@@ -410,6 +410,7 @@
             "SCHEMAS",
             "NODE_METRICS",
             "BASELINE_NODES",
+            "BASELINE_NODE_ATTRIBUTES",
             "INDEXES",
             "LOCAL_CACHE_GROUPS_IO",
             "SQL_QUERIES",
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
index 3ff00b9..cc5739b 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
@@ -965,10 +965,14 @@
      */
     @Test
     public void testBaselineViews() throws Exception {
+        String customAttr = "CUSTOM_NODE_ATTR";
+
         cleanPersistenceDir();
 
-        Ignite ignite = startGrid(getTestIgniteInstanceName(), getPdsConfiguration("node0"));
-        startGrid(getTestIgniteInstanceName(1), getPdsConfiguration("node1"));
+        Ignite ignite = startGrid(getTestIgniteInstanceName(), getPdsConfiguration("node0")
+            .setUserAttributes(F.asMap(customAttr, "val0")));
+        startGrid(getTestIgniteInstanceName(1), getPdsConfiguration("node1")
+            .setUserAttributes(F.asMap(customAttr, "val1")));
 
         ignite.cluster().active(true);
 
@@ -993,7 +997,8 @@
 
         assertEquals("node1", res.get(0).get(0));
 
-        Ignite ignite2 = startGrid(getTestIgniteInstanceName(2), getPdsConfiguration("node2"));
+        Ignite ignite2 = startGrid(getTestIgniteInstanceName(2), getPdsConfiguration("node2")
+            .setUserAttributes(F.asMap(customAttr, "val2")));
 
         assertEquals(2, execSql(ignite2, "SELECT CONSISTENT_ID FROM " + systemSchemaName() + ".BASELINE_NODES").size());
 
@@ -1003,6 +1008,60 @@
         assertEquals(1, res.size());
 
         assertEquals("node2", res.get(0).get(0));
+
+        // Check baseline node attributes.
+        assertColumnTypes(execSql("SELECT NODE_CONSISTENT_ID, NAME, VALUE FROM " + systemSchemaName() +
+                ".BASELINE_NODE_ATTRIBUTES").get(0), String.class, String.class, String.class);
+
+        // Check without filters.
+        res = execSql("SELECT NAME, VALUE FROM " + systemSchemaName() + ".BASELINE_NODE_ATTRIBUTES ORDER BY VALUE");
+
+        assertTrue(res.size() > 1);
+        assertEquals(1, F.size(res, row -> customAttr.equals(row.get(0)) && "val0".equals(row.get(1))));
+        assertEquals(1, F.size(res, row -> customAttr.equals(row.get(0)) && "val1".equals(row.get(1))));
+
+        // Check filter by node consistent ID.
+        res = execSql("SELECT NAME, VALUE FROM " + systemSchemaName() + ".BASELINE_NODE_ATTRIBUTES " +
+            "WHERE NODE_CONSISTENT_ID = ?", "node0");
+
+        assertTrue(res.size() > 1);
+        assertEquals(1, F.size(res, row -> customAttr.equals(row.get(0)) && "val0".equals(row.get(1))));
+
+        // Check filter by node consistent ID and attribute name.
+        res = execSql("SELECT NAME, VALUE FROM " + systemSchemaName() + ".BASELINE_NODE_ATTRIBUTES " +
+            "WHERE NODE_CONSISTENT_ID = ? AND NAME = ?", "node0", customAttr);
+
+        assertEquals(1, res.size());
+        assertEquals("val0", res.get(0).get(1));
+
+        // Check filter by attribute name.
+        res = execSql("SELECT NAME, VALUE FROM " + systemSchemaName() + ".BASELINE_NODE_ATTRIBUTES " +
+            "WHERE NAME = ? ORDER BY VALUE", customAttr);
+
+        assertEquals(2, res.size());
+        assertEquals("val0", res.get(0).get(1));
+        assertEquals("val1", res.get(1).get(1));
+
+        // Check that stored in BLT attribute value is shown.
+        startGrid(getTestIgniteInstanceName(1), getPdsConfiguration("node1")
+            .setUserAttributes(F.asMap(customAttr, "val3")));
+
+        res = execSql("SELECT NAME, VALUE FROM " + systemSchemaName() + ".BASELINE_NODE_ATTRIBUTES " +
+            "WHERE NODE_CONSISTENT_ID = ? AND NAME = ?", "node1", customAttr);
+
+        assertEquals(1, res.size());
+        assertEquals("val1", res.get(0).get(1));
+
+        // Check join with BASELINE_NODES view.
+        res = execSql("SELECT N.CONSISTENT_ID, NA.NAME, NA.VALUE FROM " + systemSchemaName() +
+            ".BASELINE_NODE_ATTRIBUTES NA JOIN " + systemSchemaName() + ".BASELINE_NODES N " +
+            "ON N.CONSISTENT_ID = NA.NODE_CONSISTENT_ID " +
+            "WHERE NODE_CONSISTENT_ID = ? AND NAME = ?", "node0", customAttr);
+
+        assertEquals(1, res.size());
+        assertEquals("node0", res.get(0).get(0));
+        assertEquals(customAttr, res.get(0).get(1));
+        assertEquals("val0", res.get(0).get(2));
     }
 
     /** {@inheritDoc} */