]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#714,!409] Associate global options with the server tags.
authorMarcin Siodelski <marcin@isc.org>
Wed, 3 Jul 2019 11:42:39 +0000 (13:42 +0200)
committerMarcin Siodelski <marcin@isc.org>
Thu, 4 Jul 2019 19:48:15 +0000 (15:48 -0400)
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp6.cc
src/hooks/dhcp/mysql_cb/mysql_cb_impl.cc
src/hooks/dhcp/mysql_cb/mysql_query_macros_dhcp.h
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp6_unittest.cc

index 69dfe47c596712f147d89b999a98c24e80fc92fc..8e31d674c97c24fd3cf97c7d8d0134b5649831ac 100644 (file)
@@ -115,6 +115,7 @@ public:
         DELETE_OPTION_DEF4_CODE_NAME,
         DELETE_ALL_OPTION_DEFS4,
         DELETE_OPTION4,
+        DELETE_ALL_OPTIONS4_UNASSIGNED,
         DELETE_OPTION4_SUBNET_ID,
         DELETE_OPTION4_POOL_RANGE,
         DELETE_OPTION4_SHARED_NETWORK,
@@ -1441,7 +1442,10 @@ public:
             createInputContextBinding(option),
             MySqlBinding::createNull(),
             MySqlBinding::createNull(),
-            MySqlBinding::createTimestamp(option->getModificationTime())
+            MySqlBinding::createTimestamp(option->getModificationTime()),
+            MySqlBinding::createString(tag),
+            MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
+            MySqlBinding::condCreateString(option->space_name_)
         };
 
         MySqlTransaction transaction(conn_);
@@ -1456,16 +1460,11 @@ public:
                            MySqlConfigBackendDHCPv4Impl::CREATE_AUDIT_REVISION,
                            server_selector, "global option set", false);
 
-        if (existing_option) {
-            in_bindings.push_back(MySqlBinding::createString(tag));
-            in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(option->option_->getType()));
-            in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_));
-            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4,
-                                    in_bindings);
-
-        } else {
+        if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::UPDATE_OPTION4,
+                                    in_bindings) == 0) {
+            // Remove the 3 bindings used only in case of update.
+            in_bindings.resize(in_bindings.size() - 3);
             insertOption4(server_selector, in_bindings);
-
         }
 
         transaction.commit();
@@ -1931,7 +1930,11 @@ public:
             conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::
                                     DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED,
                                     MySqlBindingCollection());
-            /// @todo delete dangling options and option definitions.
+
+            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::
+                                    DELETE_ALL_OPTIONS4_UNASSIGNED,
+                                    MySqlBindingCollection());
+            /// @todo delete option definitions.
         }
 
         transaction.commit();
@@ -1967,7 +1970,12 @@ public:
             conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::
                                     DELETE_ALL_GLOBAL_PARAMETERS4_UNASSIGNED,
                                     MySqlBindingCollection());
-            /// @todo delete dangling options and option definitions.
+
+            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv4Impl::
+                                    DELETE_ALL_OPTIONS4_UNASSIGNED,
+                                    MySqlBindingCollection());
+
+            /// @todo delete dangling option definitions.
         }
 
         transaction.commit();
@@ -2396,6 +2404,11 @@ TaggedStatementArray tagged_statements = { {
       MYSQL_DELETE_OPTION(dhcp4, AND o.scope_id = 0  AND o.code = ? AND o.space = ?)
     },
 
+    // Delete all options which are unassigned to any servers.
+    { MySqlConfigBackendDHCPv4Impl::DELETE_ALL_OPTIONS4_UNASSIGNED,
+      MYSQL_DELETE_OPTION_UNASSIGNED(dhcp4)
+    },
+
     // Delete single option from a subnet.
     { MySqlConfigBackendDHCPv4Impl::DELETE_OPTION4_SUBNET_ID,
       MYSQL_DELETE_OPTION(dhcp4,
index aeb1fdbce88bc4c31611c65a20a5abdd2c752915..8ee6917853365ca1798f0537038570d9bef786e3 100644 (file)
@@ -121,6 +121,7 @@ public:
         DELETE_OPTION_DEF6_CODE_NAME,
         DELETE_ALL_OPTION_DEFS6,
         DELETE_OPTION6,
+        DELETE_ALL_OPTIONS6_UNASSIGNED,
         DELETE_OPTION6_SUBNET_ID,
         DELETE_OPTION6_POOL_RANGE,
         DELETE_OPTION6_PD_POOL,
@@ -1653,7 +1654,10 @@ public:
             MySqlBinding::createNull(),
             MySqlBinding::createNull(),
             MySqlBinding::createTimestamp(option->getModificationTime()),
-            MySqlBinding::createNull()
+            MySqlBinding::createNull(),
+            MySqlBinding::createString(tag),
+            MySqlBinding::createInteger<uint8_t>(option->option_->getType()),
+            MySqlBinding::condCreateString(option->space_name_)
         };
 
         MySqlTransaction transaction(conn_);
@@ -1668,16 +1672,11 @@ public:
                            MySqlConfigBackendDHCPv6Impl::CREATE_AUDIT_REVISION,
                            server_selector, "global option set", false);
 
-        if (existing_option) {
-            in_bindings.push_back(MySqlBinding::createString(tag));
-            in_bindings.push_back(MySqlBinding::createInteger<uint16_t>(option->option_->getType()));
-            in_bindings.push_back(MySqlBinding::condCreateString(option->space_name_));
-            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6,
-                                    in_bindings);
-
-        } else {
+        if (conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::UPDATE_OPTION6,
+                                    in_bindings) == 0) {
+            // Remove the 3 bindings used only in case of update.
+            in_bindings.resize(in_bindings.size() - 3);
             insertOption6(server_selector, in_bindings);
-
         }
 
         transaction.commit();
@@ -2245,7 +2244,11 @@ public:
             conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::
                                     DELETE_ALL_GLOBAL_PARAMETERS6_UNASSIGNED,
                                     MySqlBindingCollection());
-            /// @todo delete dangling options and option definitions.
+
+            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::
+                                    DELETE_ALL_OPTIONS6_UNASSIGNED,
+                                    MySqlBindingCollection());
+            /// @todo delete dangling option definitions.
         }
 
         transaction.commit();
@@ -2281,7 +2284,11 @@ public:
             conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::
                                     DELETE_ALL_GLOBAL_PARAMETERS6_UNASSIGNED,
                                     MySqlBindingCollection());
-            /// @todo delete dangling options and option definitions.
+
+            conn_.updateDeleteQuery(MySqlConfigBackendDHCPv6Impl::
+                                    DELETE_ALL_OPTIONS6_UNASSIGNED,
+                                    MySqlBindingCollection());
+            /// @todo delete dangling option definitions.
         }
 
         transaction.commit();
@@ -2753,6 +2760,11 @@ TaggedStatementArray tagged_statements = { {
       MYSQL_DELETE_OPTION(dhcp6, AND o.scope_id = 0  AND o.code = ? AND o.space = ?)
     },
 
+    // Delete all options which are unassigned to any servers.
+    { MySqlConfigBackendDHCPv6Impl::DELETE_ALL_OPTIONS6_UNASSIGNED,
+      MYSQL_DELETE_OPTION_UNASSIGNED(dhcp6)
+    },
+
     // Delete single option from a subnet.
     { MySqlConfigBackendDHCPv6Impl::DELETE_OPTION6_SUBNET_ID,
       MYSQL_DELETE_OPTION(dhcp6,
index dd6a4421f14d2c53efaa2ccef142ee66c43e15f0..dfa24124b898d537f182b115cd18d60362221a2b 100644 (file)
@@ -762,8 +762,10 @@ MySqlConfigBackendImpl::getOptions(const int index,
 
     uint64_t last_option_id = 0;
 
+    OptionContainer local_options;
+
     conn_.selectQuery(index, in_bindings, out_bindings,
-                      [this, universe, &options, &last_option_id]
+                      [this, universe, &local_options, &last_option_id]
                       (MySqlBindingCollection& out_bindings) {
         // Parse option.
         if (!out_bindings[0]->amNull() &&
@@ -774,11 +776,46 @@ MySqlConfigBackendImpl::getOptions(const int index,
             OptionDescriptorPtr desc = processOptionRow(universe, out_bindings.begin());
             if (desc) {
                 // server_tag for the global option
-                desc->setServerTag(out_bindings[12]->getString());
-                static_cast<void>(options.push_back(*desc));
+                ServerTag last_option_server_tag(out_bindings[12]->getString());
+                desc->setServerTag(last_option_server_tag.get());
+
+                // If we're fetching options for a given server (explicit server
+                // tag is provided), it takes precedence over the same option
+                // specified for all servers. Therefore, we check if the given
+                // option already exists and belongs to 'all'.
+                auto& index = local_options.get<1>();
+                auto existing_it_pair = index.equal_range(desc->option_->getType());
+                auto existing_it = existing_it_pair.first;
+                bool found = false;
+                for ( ; existing_it != existing_it_pair.second; ++existing_it) {
+                    if (existing_it->space_name_ == desc->space_name_) {
+                        found = true;
+                        // This option was already fetched. Let's check if we should
+                        // replace it or not.
+                        if (!last_option_server_tag.amAll() && existing_it->hasAllServerTag()) {
+                            index.replace(existing_it, *desc);
+                            return;
+                        }
+                        break;
+                    }
+                }
+
+                // If there is no such global option yet or the existing option
+                // belongs to a different server and the inserted option is not
+                // for all servers.
+                if (!found ||
+                    (!existing_it->hasServerTag(last_option_server_tag) &&
+                     !last_option_server_tag.amAll())) {
+                    static_cast<void>(local_options.push_back(*desc));
+                }
             }
         }
     });
+
+    // Append the options fetched by this function into the container supplied
+    // by the caller. The container supplied by the caller may already hold
+    // some options fetched for other server tags.
+    options.insert(options.end(), local_options.begin(), local_options.end());
 }
 
 OptionDescriptorPtr
index 1f7c9efb23603728eace0826432d62fd843fad62..d257f0821211b5e849d7709336f2c3d2d067592f 100644 (file)
@@ -353,7 +353,7 @@ namespace {
     "INNER JOIN " #table_prefix "_server AS s" \
     "  ON a.server_id = s.id " \
     "WHERE (s.tag = ? OR s.id = 1) " #__VA_ARGS__ \
-    " ORDER BY o.option_id"
+    " ORDER BY o.option_id, s.id"
 
 #define MYSQL_GET_OPTION4(...) \
     MYSQL_GET_OPTION_COMMON(dhcp4, "", __VA_ARGS__)
@@ -667,6 +667,14 @@ namespace {
     "WHERE s.tag = ? " #__VA_ARGS__
 #endif
 
+#ifndef MYSQL_DELETE_OPTION_UNASSIGNED
+#define MYSQL_DELETE_OPTION_UNASSIGNED(table_prefix, ...) \
+    "DELETE o FROM " #table_prefix "_options AS o " \
+    "LEFT JOIN " #table_prefix "_options_server AS a " \
+    "  ON o.option_id = a.option_id " \
+    "WHERE a.option_id IS NULL " #__VA_ARGS__
+#endif
+
 #ifndef MYSQL_DELETE_OPTION_POOL_RANGE
 #define MYSQL_DELETE_OPTION_POOL_RANGE(table_prefix, ...) \
     "DELETE o FROM " #table_prefix "_options AS o " \
index 8dca6fe89045a336f3c65489f0093cf0cfd35c4e..b8060499b1e497b60fa973f9d2695215e0ebae24 100644 (file)
@@ -302,6 +302,18 @@ public:
         desc.space_name_ = "isc";
         test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
 
+        desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                          true, false, "my-boot-file-2");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+                                          true, false, "my-boot-file-3");
+        desc.space_name_ = DHCP4_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
         // Add definitions for DHCPv4 non-standard options in case we need to
         // compare subnets, networks and pools in JSON format. In that case,
         // the @c toElement functions require option definitions to generate the
@@ -1931,8 +1943,9 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteOption4) {
                                 opt_boot_file_name);
 
     // Retrieve the option again and make sure that updates were
-    // properly propagated to the database.
-    returned_opt_boot_file_name = cbptr_->getOption4(ServerSelector::ALL(),
+    // properly propagated to the database. Use explicit server selector
+    // which should also return this option.
+    returned_opt_boot_file_name = cbptr_->getOption4(ServerSelector::ONE("server1"),
                                                      opt_boot_file_name->option_->getType(),
                                                      opt_boot_file_name->space_name_);
     ASSERT_TRUE(returned_opt_boot_file_name);
@@ -1971,6 +1984,189 @@ TEST_F(MySqlConfigBackendDHCPv4Test, createUpdateDeleteOption4) {
     }
 }
 
+// This test verifies that it is possible to differentiate between the
+// global options by server tag and that the option specified for the
+// particular server overrides the value specified for all servers.
+TEST_F(MySqlConfigBackendDHCPv4Test, globalOptions4WithServerTags) {
+    OptionDescriptorPtr opt_boot_file_name1 = test_options_[0];
+    OptionDescriptorPtr opt_boot_file_name2 = test_options_[6];
+    OptionDescriptorPtr opt_boot_file_name3 = test_options_[7];
+
+    EXPECT_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"),
+                                             opt_boot_file_name1),
+                 DbOperationError);
+
+    // Create two servers.
+    EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[1]));
+    {
+        SCOPED_TRACE("server1 is created");
+        testNewAuditEntry("dhcp4_server",
+                          AuditEntry::ModificationType::CREATE,
+                          "server set");
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateServer4(test_servers_[2]));
+    {
+        SCOPED_TRACE("server2 is created");
+        testNewAuditEntry("dhcp4_server",
+                          AuditEntry::ModificationType::CREATE,
+                          "server set");
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"),
+                                                opt_boot_file_name1));
+    {
+        SCOPED_TRACE("global option for server1 is set");
+        // The value of 3 means there should be 3 audit entries available for the
+        // server1, two that indicate creation of the servers and one that we
+        // validate, which sets the global option.
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::CREATE,
+                          "global option set",
+                          ServerSelector::ONE("server1"),
+                          3, 1);
+
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server2"),
+                                                opt_boot_file_name2));
+    {
+        SCOPED_TRACE("global option for server2 is set");
+        // Same as in case of the server1, there should be 3 audit entries and
+        // we validate one of them.
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::CREATE,
+                          "global option set",
+                          ServerSelector::ONE("server2"),
+                          3, 1);
+
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateOption4(ServerSelector::ALL(),
+                                                opt_boot_file_name3));
+    {
+        SCOPED_TRACE("global option for all servers is set");
+        // There should be one new audit entry for all servers. It logs
+        // the insertion of the global option.
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::CREATE,
+                          "global option set",
+                          ServerSelector::ALL(),
+                          1, 1);
+
+    }
+
+    OptionDescriptorPtr returned_option;
+
+    // Try to fetch the option specified for all servers. It should return
+    // the third option.
+    EXPECT_NO_THROW(
+        returned_option = cbptr_->getOption4(ServerSelector::ALL(),
+                                             opt_boot_file_name3->option_->getType(),
+                                             opt_boot_file_name3->space_name_);
+    );
+    ASSERT_TRUE(returned_option);
+    testOptionsEquivalent(*opt_boot_file_name3, *returned_option);
+
+    // Try to fetch the option specified for the server1. It should override the
+    // option specified for all servers.
+    EXPECT_NO_THROW(
+        returned_option = cbptr_->getOption4(ServerSelector::ONE("server1"),
+                                             opt_boot_file_name1->option_->getType(),
+                                             opt_boot_file_name1->space_name_);
+    );
+    ASSERT_TRUE(returned_option);
+    testOptionsEquivalent(*opt_boot_file_name1, *returned_option);
+
+    // The same in case of the server2.
+    EXPECT_NO_THROW(
+        returned_option = cbptr_->getOption4(ServerSelector::ONE("server2"),
+                                             opt_boot_file_name2->option_->getType(),
+                                             opt_boot_file_name2->space_name_);
+    );
+    ASSERT_TRUE(returned_option);
+    testOptionsEquivalent(*opt_boot_file_name2, *returned_option);
+
+    OptionContainer returned_options;
+
+    // Try to fetch the collection of global options for the server1, server2
+    // and server3. The server3 does not have an explicit value so for this server
+    // we should get the option associated with "all" servers.
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions4(ServerSelector::
+                                                  MULTIPLE({ "server1", "server2",
+                                                             "server3" }));
+    );
+    ASSERT_EQ(3, returned_options.size());
+
+    // Check that expected options have been returned.
+    auto current_option = returned_options.begin();
+    testOptionsEquivalent(*opt_boot_file_name1, *current_option);
+    testOptionsEquivalent(*opt_boot_file_name2, *(++current_option));
+    testOptionsEquivalent(*opt_boot_file_name3, *(++current_option));
+
+    // Try to fetch the collection of options specified for all servers.
+    // This excludes the options specific to server1 and server2. It returns
+    // only the common ones.
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+    );
+    ASSERT_EQ(1, returned_options.size());
+    testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+    // Delete the server1. It should remove associations of this server with the
+    // option and the option itself.
+    EXPECT_NO_THROW(cbptr_->deleteServer4(ServerTag("server1")));
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions4(ServerSelector::ONE("server1"));
+    );
+    ASSERT_EQ(1, returned_options.size());
+    testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+    {
+        SCOPED_TRACE("DELETE audit entry for the global option after server deletion");
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::DELETE,
+                          "deleting a server", ServerSelector::ONE("server1"),
+                          2, 1);
+    }
+
+    // Attempt to delete global option for server1.
+    uint64_t deleted_num = 0;
+    EXPECT_NO_THROW(deleted_num = cbptr_->deleteOption4(ServerSelector::ONE("server1"),
+                                                        opt_boot_file_name1->option_->getType(),
+                                                        opt_boot_file_name1->space_name_));
+    EXPECT_EQ(0, deleted_num);
+
+    // Deleting the existing option for server2 should succeed.
+    EXPECT_NO_THROW(deleted_num = cbptr_->deleteOption4(ServerSelector::ONE("server2"),
+                                                        opt_boot_file_name2->option_->getType(),
+                                                        opt_boot_file_name2->space_name_));
+    EXPECT_EQ(1, deleted_num);
+
+    // Create this option again to test that deletion of all servers removes it too.
+    EXPECT_NO_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server2"),
+                                                opt_boot_file_name2));
+
+    // Delete all servers, except 'all'.
+    EXPECT_NO_THROW(deleted_num = cbptr_->deleteAllServers4());
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+    );
+    EXPECT_EQ(1, deleted_num);
+    ASSERT_EQ(1, returned_options.size());
+    testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+    {
+        SCOPED_TRACE("DELETE audit entry for the global option after deletion of"
+                     " all servers");
+        testNewAuditEntry("dhcp4_options",
+                          AuditEntry::ModificationType::DELETE,
+                          "deleting all servers", ServerSelector::ONE("server2"),
+                          4, 1);
+    }
+}
+
 // This test verifies that all global options can be retrieved.
 TEST_F(MySqlConfigBackendDHCPv4Test, getAllOptions4) {
     // Add three global options to the database.
index b788ae89904a2199fa7b4f7e53275af59950fb15..f92f3d9dddbfde5db473eedac8879b57fdef8926 100644 (file)
@@ -343,6 +343,18 @@ public:
         desc.space_name_ = "isc";
         test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
 
+        desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+                                          true, false, "my-timezone-2");
+        desc.space_name_ = DHCP6_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+        desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+                                          true, false, "my-timezone-3");
+        desc.space_name_ = DHCP6_OPTION_SPACE;
+        desc.setContext(user_context);
+        test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
         // Add definitions for DHCPv6 non-standard options in case we need to
         // compare subnets, networks and pools in JSON format. In that case,
         // the @c toElement functions require option definitions to generate the
@@ -1953,8 +1965,9 @@ TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteOption6) {
                                 opt_posix_timezone);
 
     // Retrieve the option again and make sure that updates were
-    // properly propagated to the database.
-    returned_opt_posix_timezone = cbptr_->getOption6(ServerSelector::ALL(),
+    // properly propagated to the database. Use explicit server selector
+    // which should also return this option.
+    returned_opt_posix_timezone = cbptr_->getOption6(ServerSelector::ONE("server1"),
                                                      opt_posix_timezone->option_->getType(),
                                                      opt_posix_timezone->space_name_);
     ASSERT_TRUE(returned_opt_posix_timezone);
@@ -1994,6 +2007,189 @@ TEST_F(MySqlConfigBackendDHCPv6Test, createUpdateDeleteOption6) {
     }
 }
 
+// This test verifies that it is possible to differentiate between the
+// global options by server tag and that the option specified for the
+// particular server overrides the value specified for all servers.
+TEST_F(MySqlConfigBackendDHCPv6Test, globalOptions6WithServerTags) {
+    OptionDescriptorPtr opt_timezone1 = test_options_[0];
+    OptionDescriptorPtr opt_timezone2 = test_options_[6];
+    OptionDescriptorPtr opt_timezone3 = test_options_[7];
+
+    EXPECT_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"),
+                                             opt_timezone1),
+                 DbOperationError);
+
+    // Create two servers.
+    EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[1]));
+    {
+        SCOPED_TRACE("server1 is created");
+        testNewAuditEntry("dhcp6_server",
+                          AuditEntry::ModificationType::CREATE,
+                          "server set");
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateServer6(test_servers_[2]));
+    {
+        SCOPED_TRACE("server2 is created");
+        testNewAuditEntry("dhcp6_server",
+                          AuditEntry::ModificationType::CREATE,
+                          "server set");
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"),
+                                                opt_timezone1));
+    {
+        SCOPED_TRACE("global option for server1 is set");
+        // The value of 3 means there should be 3 audit entries available for the
+        // server1, two that indicate creation of the servers and one that we
+        // validate, which sets the global option.
+        testNewAuditEntry("dhcp6_options",
+                          AuditEntry::ModificationType::CREATE,
+                          "global option set",
+                          ServerSelector::ONE("server1"),
+                          3, 1);
+
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"),
+                                                opt_timezone2));
+    {
+        SCOPED_TRACE("global option for server2 is set");
+        // Same as in case of the server1, there should be 3 audit entries and
+        // we validate one of them.
+        testNewAuditEntry("dhcp6_options",
+                          AuditEntry::ModificationType::CREATE,
+                          "global option set",
+                          ServerSelector::ONE("server2"),
+                          3, 1);
+
+    }
+
+    EXPECT_NO_THROW(cbptr_->createUpdateOption6(ServerSelector::ALL(),
+                                                opt_timezone3));
+    {
+        SCOPED_TRACE("global option for all servers is set");
+        // There should be one new audit entry for all servers. It logs
+        // the insertion of the global option.
+        testNewAuditEntry("dhcp6_options",
+                          AuditEntry::ModificationType::CREATE,
+                          "global option set",
+                          ServerSelector::ALL(),
+                          1, 1);
+
+    }
+
+    OptionDescriptorPtr returned_option;
+
+    // Try to fetch the option specified for all servers. It should return
+    // the third option.
+    EXPECT_NO_THROW(
+        returned_option = cbptr_->getOption6(ServerSelector::ALL(),
+                                             opt_timezone3->option_->getType(),
+                                             opt_timezone3->space_name_);
+    );
+    ASSERT_TRUE(returned_option);
+    testOptionsEquivalent(*opt_timezone3, *returned_option);
+
+    // Try to fetch the option specified for the server1. It should override the
+    // option specified for all servers.
+    EXPECT_NO_THROW(
+        returned_option = cbptr_->getOption6(ServerSelector::ONE("server1"),
+                                             opt_timezone1->option_->getType(),
+                                             opt_timezone1->space_name_);
+    );
+    ASSERT_TRUE(returned_option);
+    testOptionsEquivalent(*opt_timezone1, *returned_option);
+
+    // The same in case of the server2.
+    EXPECT_NO_THROW(
+        returned_option = cbptr_->getOption6(ServerSelector::ONE("server2"),
+                                             opt_timezone2->option_->getType(),
+                                             opt_timezone2->space_name_);
+    );
+    ASSERT_TRUE(returned_option);
+    testOptionsEquivalent(*opt_timezone2, *returned_option);
+
+    OptionContainer returned_options;
+
+    // Try to fetch the collection of global options for the server1, server2
+    // and server3. The server3 does not have an explicit value so for this server
+    // we should get the option associated with "all" servers.
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions6(ServerSelector::
+                                                  MULTIPLE({ "server1", "server2",
+                                                             "server3" }));
+    );
+    ASSERT_EQ(3, returned_options.size());
+
+    // Check that expected options have been returned.
+    auto current_option = returned_options.begin();
+    testOptionsEquivalent(*opt_timezone1, *current_option);
+    testOptionsEquivalent(*opt_timezone2, *(++current_option));
+    testOptionsEquivalent(*opt_timezone3, *(++current_option));
+
+    // Try to fetch the collection of options specified for all servers.
+    // This excludes the options specific to server1 and server2. It returns
+    // only the common ones.
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+    );
+    ASSERT_EQ(1, returned_options.size());
+    testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+    // Delete the server1. It should remove associations of this server with the
+    // option and the option itself.
+    EXPECT_NO_THROW(cbptr_->deleteServer6(ServerTag("server1")));
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1"));
+    );
+    ASSERT_EQ(1, returned_options.size());
+    testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+    {
+        SCOPED_TRACE("DELETE audit entry for the global option after server deletion");
+        testNewAuditEntry("dhcp6_options",
+                          AuditEntry::ModificationType::DELETE,
+                          "deleting a server", ServerSelector::ONE("server1"),
+                          2, 1);
+    }
+
+    // Attempt to delete global option for server1.
+    uint64_t deleted_num = 0;
+    EXPECT_NO_THROW(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server1"),
+                                                        opt_timezone1->option_->getType(),
+                                                        opt_timezone1->space_name_));
+    EXPECT_EQ(0, deleted_num);
+
+    // Deleting the existing option for server2 should succeed.
+    EXPECT_NO_THROW(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server2"),
+                                                        opt_timezone2->option_->getType(),
+                                                        opt_timezone2->space_name_));
+    EXPECT_EQ(1, deleted_num);
+
+    // Create this option again to test that deletion of all servers removes it too.
+    EXPECT_NO_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"),
+                                                opt_timezone2));
+
+    // Delete all servers, except 'all'.
+    EXPECT_NO_THROW(deleted_num = cbptr_->deleteAllServers6());
+    EXPECT_NO_THROW(
+        returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+    );
+    EXPECT_EQ(1, deleted_num);
+    ASSERT_EQ(1, returned_options.size());
+    testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+    {
+        SCOPED_TRACE("DELETE audit entry for the global option after deletion of"
+                     " all servers");
+        testNewAuditEntry("dhcp6_options",
+                          AuditEntry::ModificationType::DELETE,
+                          "deleting all servers", ServerSelector::ONE("server2"),
+                          4, 1);
+    }
+}
+
 // This test verifies that all global options can be retrieved.
 TEST_F(MySqlConfigBackendDHCPv6Test, getAllOptions6) {
     // Add three global options to the database.