Problem/Motivation

While working on #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method, I noticed it was unfortunately necessary to rebuild the router whenever you imported (i.e. deployed) RestResourceConfig config entities. This should happen automatically thanks to a ConfigEvents::SAVE event subscriber.

Worse, you actually even need to call drupal_flush_all_caches(): just calling $this->container->get('router.builder')->rebuild() is insufficient! (I have no idea why yet.)

When doing an import through the UI, this means you need to go to /admin/config/development/performance and click the Clear all caches button. If a developer forgets this step, then their REST routes won't work.

Proposed resolution

  1. Routing system:
    1. Add http_response cache tag to all cacheable responses.
    2. Let route rebuilder invalidate the http_response_ cache tag after every rebuild.
  2. REST module:
    1. Make RestResourceConfig::post(Save|Delete) mark the router as needing a rebuild.
    2. Implement a REST config save event subscriber (for the rest.settings config) that marks the router as needing a rebuild.

Together this means that:

  1. every router rebuild will now invalidate all responses. Cached 404 responses may become 200s, 200s may become 403s, 404s may become 403s, and so on. Hence all cached responses need to be rebuilt.
  2. whenever RestResourceConfig config entities or rest.settings simple config change, mark the router as needing a rebuild, which will trigger the above.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None.

CommentFileSizeAuthor
#76 2815845-76.patch30.47 KBalexpott
#76 71-76-interdiff.txt560 bytesalexpott
#71 2815845-71.patch30.68 KBwim leers
#71 interdiff-64-71.txt1.08 KBwim leers
#68 interdiff.txt1.02 KBdawehner
#68 2815845-68.patch30.87 KBdawehner
#64 2815845-64.patch30.61 KBwim leers
#64 interdiff.txt1.36 KBwim leers
#61 2815845-62.patch29.73 KBwim leers
#61 interdiff.txt3.79 KBwim leers
#60 2815845-61.patch29.69 KBwim leers
#60 interdiff.txt2.57 KBwim leers
#59 2815845-60.patch27.3 KBwim leers
#59 interdiff.txt2.08 KBwim leers
#52 interdiff.txt1.79 KBwim leers
#52 2815845-52.patch25.71 KBwim leers
#49 interdiff.txt2.53 KBdawehner
#49 2815845-49.patch25.44 KBdawehner
#44 interdiff.txt5.98 KBdawehner
#44 2815845-44.patch25.88 KBdawehner
#42 interdiff.txt1.67 KBdawehner
#42 2815845-42.patch25.93 KBdawehner
#41 interdiff.txt2.31 KBdawehner
#41 2815845-41.patch1.67 KBdawehner
#38 interdiff.txt4.44 KBdawehner
#38 2815845-38.patch24.14 KBdawehner
#36 2815845-36.patch25.2 KBdawehner
#36 interdiff.txt4.89 KBdawehner
#34 2815845-34.patch21.57 KBdawehner
#34 interdiff.txt766 bytesdawehner
#32 interdiff.txt15.02 KBdawehner
#32 2815845-32.patch21.5 KBdawehner
#29 interdiff.txt4.19 KBdawehner
#29 2815845-29.patch11.08 KBdawehner
#26 interdiff.txt7.13 KBdawehner
#26 2815845-26.patch9.14 KBdawehner
#19 interdiff.txt1.88 KBdawehner
#19 2815845-19.patch3.9 KBdawehner
#17 interdiff.txt643 bytesdawehner
#17 2815845-17.patch2.65 KBdawehner
#15 interdiff.txt700 byteswim leers
#15 2815845-15.patch2.29 KBwim leers
#13 2815845-13.patch1.63 KBwim leers
#9 2815845-9.patch1.45 KBdawehner
#9 interdiff.txt591 bytesdawehner
#6 2815845-6.patch1.48 KBdawehner

Comments

Wim Leers created an issue. See original summary.

dawehner’s picture

IMHO the right thing would be to call to \Drupal\Core\Routing\RouteBuilder::setRebuildNeeded

wim leers’s picture

WFM

swentel’s picture

OMG, you probably just saved me endless drupal_flush_all_caches() in custom tests!

wim leers’s picture

#4: You're doing a lot of tests with REST resources?

dawehner’s picture

Status: Active » Needs review
StatusFileSize
new1.48 KB

There we go.

Status: Needs review » Needs work

The last submitted patch, 6: 2815845-6.patch, failed testing.

wim leers’s picture

  1. +++ b/core/modules/rest/src/Entity/RestResourceConfig.php
    @@ -263,4 +264,13 @@ protected function normalizeRestMethod($method) {
    +  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    +    parent::postSave($storage, $update);
    +
    +    \Drupal::service('router.builder')->setRebuildNeeded();
    +  }
    

    Oh, interesting! I thought this would need a \Drupal\Core\Config\ConfigEvents::SAVE subscriber.

    Is this better? Why? Should we document that?

    (Asking for the patch, but also because I'd genuinely like to know.)

  2. The patch is failing, for example in \Drupal\dblog\Tests\Rest\DbLogResourceTest.

    This is why:

    +++ b/core/modules/rest/src/Tests/RESTTestBase.php
    @@ -411,15 +411,12 @@ protected function enableService($resource_type, $method = 'GET', $format = NULL
    -    $this->rebuildCache();
    ...
    -    // Rebuild routing cache, so that the REST API paths are available.
    -    $this->container->get('router.builder')->rebuild();
    

    I'm afraid we really need this router to be rebuilt too, because otherwise the test cannot access the router to generate URLs to access.

  3. We still need a BrowserTestBase test that tests as a regular user: making config changes causes route changes to be available on the next request.
dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new591 bytes
new1.45 KB

Is this better? Why? Should we document that?

(Asking for the patch, but also because I'd genuinely like to know.)

I just copied it from views. I think the rule might be like the following: if your particular plugin inside the entity needs this change, go with a subscriber. If its needed for every of those entity usages, go with ::postSave

I'm afraid we really need this router to be rebuilt too, because otherwise the test cannot access the router to generate URLs to access.

Let's see whether conditional rebuilds might help.

wim leers’s picture

I think the rule might be like the following

Sounds good.

Let's see whether conditional rebuilds might help.

+1

Status: Needs review » Needs work

The last submitted patch, 9: 2815845-9.patch, failed testing.

wim leers’s picture

Better, but not there yet.

Fails with Call to a member function getResourcePlugin() on a non-object, only for POST and PATCH (so GET and DELETE work fine). This means that routing to RequestHandler::handle() works, but this results in no entity being loaded:

    $resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config');
    /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
    $resource_config = $this->resourceStorage->load($resource_config_id);
    $resource = $resource_config->getResourcePlugin();

Interesting.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.63 KB

Rebased.

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs review » Needs work

Since #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method, there's another place where we need to remove the forced rebuilding: ResourceTestBase.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new2.29 KB
new700 bytes

This is what also needed to be removed per #14.

Status: Needs review » Needs work

The last submitted patch, 15: 2815845-15.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new2.65 KB
new643 bytes

I tried this out, certainly didn't helped.

Status: Needs review » Needs work

The last submitted patch, 17: 2815845-17.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new3.9 KB
new1.88 KB

This could fix a good amount of the failures, well, when invalidating the router. Note: We need to invalidate page_cache && dynamic_page_cache on a router rebuild.

Status: Needs review » Needs work

The last submitted patch, 19: 2815845-19.patch, failed testing.

wim leers’s picture

Note: We need to invalidate page_cache && dynamic_page_cache on a router rebuild.

REST resource responses have config:rest.resource.* cache tags, corresponding to the RestResourceConfig entities. But that doesn't help with cached 4xx responses, and that's what's going on here.

Wouldn't the more elegant solution therefore be to ensure that all 4xx responses for REST have the RestResourceConfig list cache tag?

dawehner’s picture

Wouldn't the more elegant solution therefore be to ensure that all 4xx responses for REST have the RestResourceConfig list cache tag?

Conceptually for me the route_match cache tag should be on every response. Technically route rebuilding could have arbitrary sideeffects.

wim leers’s picture

I just remembered that emptying Page Cache & Dynamic Page Cache explicitly is not an acceptable solution, because the same cannot be done for reverse proxies.

So, whatever we do, it must only use cache tag-based invalidation.

Conceptually for me the route_match cache tag should be on every response. Technically route rebuilding could have arbitrary sideeffects.

That's a good point. So should we add a cache tag for that to all responses then?

berdir’s picture

I think we discussed before that we should have a cache tag that allows us to invaidate all internal and external "full-page" caches. if route_match works for that fine with me.

wim leers’s picture

#24: exactly.

We've used the rendered cache tag for that purpose, but it only works for HTML responses. This new cache tag would work for all responses.

Perhaps we want an even more generic cache tag? For example, drupal? There are other reasons to invalidate all responses, such as deploying new modules, in which case route_match is a poor name.

dawehner’s picture

StatusFileSize
new9.14 KB
new7.13 KB

What about using http_response, as this adds a little bit more semanticness?

Here is an experimental patch which will see how many failures we could get.

dawehner’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 26: 2815845-26.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new11.08 KB
new4.19 KB

This should/could fix a lot of the failures ...

Status: Needs review » Needs work

The last submitted patch, 29: 2815845-29.patch, failed testing.

wim leers’s picture

#26: sounds good!

#29: patch is looking good. Just two remarks for now:

  1. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -135,11 +135,17 @@ public function onRespond(FilterResponseEvent $event) {
    +    // Always add the 'http_response' cache tag to be able to invalidate every
    +    // response.
    +    $response->getCacheableMetadata()->addCacheTags(['http_response']);
    

    Shouldn't this also be done for responses to subrequests?

  2. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -135,11 +135,17 @@ public function onRespond(FilterResponseEvent $event) {
    -      $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags()));
    +      $tags = $response_cacheability->getCacheTags();
    +
    +      $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $tags));
    

    This change looks like a debugging leftover?

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new21.5 KB
new15.02 KB

Thank you for the review wim!

This change looks like a debugging leftover?

It totally is indeed!

Shouldn't this also be done for responses to subrequests?

Great point!

Let's see whether I caught every instance ...

Status: Needs review » Needs work

The last submitted patch, 32: 2815845-32.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new766 bytes
new21.57 KB

This should fix a good amount of them :)

Status: Needs review » Needs work

The last submitted patch, 34: 2815845-34.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new4.89 KB
new25.2 KB

Let's see :)

Status: Needs review » Needs work

The last submitted patch, 36: 2815845-36.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new24.14 KB
new4.44 KB

Status: Needs review » Needs work

The last submitted patch, 38: 2815845-38.patch, failed testing.

wim leers’s picture

So close!

dawehner’s picture

StatusFileSize
new1.67 KB
new2.31 KB

I debugged for a while. I think this should fix all the remaining issues ..

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new25.93 KB
new1.67 KB

Ignore the last comment. I totally messed up creating the patch files.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs review » Needs work
Issue tags: +D8 cacheability

This is looking excellent. I have only nitpicks basically.

  1. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -89,6 +89,21 @@ public function __construct(LanguageManagerInterface $language_manager, ConfigFa
    +   * Sets extra headers on any responses, also subprocess ones.
    

    on any response, also subrequest ones.

  2. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -89,6 +89,21 @@ public function __construct(LanguageManagerInterface $language_manager, ConfigFa
    +    // response.
    

    …, for example after rebuilding routes.

  3. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -284,6 +299,8 @@ protected function setExpiresNoCache(Response $response) {
    +    // Execute before the onRespond method.
    +    $events[KernelEvents::RESPONSE][] = array('onAllResponds', 16);
    

    But why 16? Why not 7, or 77? Let's document the rationale for this number.

  4. +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    @@ -196,9 +196,6 @@ public function setUp() {
    -    // @todo Remove this in https://www.drupal.org/node/2815845.
    -    drupal_flush_all_caches();
    

    <3

  5. +++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
    @@ -37,6 +38,14 @@
    +  public function setUp() {
    +    parent::setUp();
    +
    +    // We don't want to ship with a node resource by default for those tests.
    +    RestResourceConfig::load('entity.node')->delete();
    +  }
    

    This can be deleted, \Drupal\Tests\rest\Functional\ResourceTestBase::setUp() is already taking care of that :)

    It does this:

        // Ensure there's a clean slate: delete all REST resource config entities.
        $this->resourceConfigStorage->delete($this->resourceConfigStorage->loadMultiple());
    
  6. +++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
    @@ -401,6 +401,7 @@ public function testReferencedEntity() {
    +    debug($referencing_entity_cache_tags);
    

    This is a debug leftover.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new25.88 KB
new5.98 KB

Thank you for your review wim!

But why 16? Why not 7, or 77? Let's document the rationale for this number.

If there would be just one :)

So some debugging latter ... we need to rebuild the router also when we delete resource configs, otherwise old entries stick in there. Sadly this causes some other failures later.

wim leers’s picture

  1. +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
    @@ -299,7 +299,8 @@ protected function setExpiresNoCache(Response $response) {
    +    // Execute before the onRespond method. We went with prio 16 as we want to
    +    // run before static::onRespond().
    

    This "16" is still a mystery to me.

  2. +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    @@ -446,7 +446,7 @@ public function testGet() {
    -  public function testPost() {
    +  public function ptestPost() {
    
    @@ -640,7 +640,7 @@ public function testPost() {
    -  public function testPatch() {
    +  public function ptestPatch() {
    
    @@ -846,7 +846,7 @@ public function testPatch() {
    -  public function testDelete() {
    +  public function ptestDelete() {
    

    Oops :)

  3. +++ b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
    @@ -130,6 +130,9 @@ public function setUp() {
    +    // If we remove resources we also need to ensure they don't stick in the
    +    // router.
    +    \Drupal::service('router.builder')->rebuild();
    

    Why do we need this now? :( Because the router used by the test itself is otherwise outdated?

  4. +++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
    @@ -4,6 +4,7 @@
    +use Drupal\rest\Entity\RestResourceConfig;
    

    This can now also be deleted :)

The last submitted patch, 42: 2815845-42.patch, failed testing.

dawehner’s picture

Oops :)

The tests won't pass anyway ... :)

This "16" is still a mystery to me.

For me as well. Well, I had no reason beside it being > 0 + some distance in case someone wants to add some more cache tags.

Why do we need this now? :( Because the router used by the test itself is otherwise outdated?

Yes. Before deleting the routes the node hal_json route is in there.

Status: Needs review » Needs work

The last submitted patch, 44: 2815845-44.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new25.44 KB
new2.53 KB

... Alright, so some failures seems to be related with line 317 still being cached:

    $response = $this->request('GET', $url, $request_options);
    if ($has_canonical_url && (!static::$auth || static::$auth === 'cookie')) {
      $this->assertSame(403, $response->getStatusCode());
    }
    else {
      $this->assert406Response($response);
    }

Alright, why is this still cached?

  public function invalidateTags(array $tags) {
    try {
      foreach ($tags as $tag) {
        // Only invalidate tags once per request unless they are written again.
        if (isset($this->invalidatedTags[$tag])) {
          continue;
        }

in \Drupal\Core\Cache\DatabaseCacheTagsChecksum::invalidateTags. When you invalidate cache tags multiple times, which happens when you rebuild the router multiple times in the test, the $this->invalidatedTags map has our tag stored. This means that invalidating the caches a second time on a second results in no invalidation.

One solution would be to reset the internal states, which happens in core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php:211 as well.

wim leers’s picture

One solution would be to reset the internal states, which happens in core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php:211 as well.

I think that's the only solution then.

I'd create a new issue and add a TODO to both places that use this hack that points to that issue. That new issue would say something like make \Drupal\Core\Cache\DatabaseCacheTagsChecksum work in all situations, not just a per-request situation. What do you think? I'd be happy to create that issue for you.

dawehner’s picture

That new issue would say something like make \Drupal\Core\Cache\DatabaseCacheTagsChecksum work in all situations, not just a per-request situation. What do you think? I'd be happy to create that issue for you.

That would make sense, well, to be honest our test is executing code both on a production system as well as on the test instance. This is the main issue we deal with. Too bad that we will probably never be able to deploy rest configuration via a POST request, as we kinda have a dependency problem.

wim leers’s picture

StatusFileSize
new25.71 KB
new1.79 KB

Actually, reading the explanation + interdiff in #49 again and also reading my concerns/criticism about that in #50 again, makes me disagree with myself.

I agree with @dawehner's assessment in #51:

well, to be honest our test is executing code both on a production system as well as on the test instance. This is the main issue we deal with.

There's no way around that.

But I do think we can make that a bit more clear:

  1. +++ b/core/modules/rest/src/Entity/RestResourceConfig.php
    @@ -255,4 +256,22 @@ protected function normalizeRestMethod($method) {
    +  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    +    parent::postSave($storage, $update);
    +
    +    \Drupal::service('router.builder')->setRebuildNeeded();
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function postDelete(EntityStorageInterface $storage, array $entities) {
    +    parent::postDelete($storage, $entities);
    +
    +    \Drupal::service('router.builder')->setRebuildNeeded();
    +  }
    

    These are the critical changes: these ensure that routes are rebuilt when necessary :)

  2. +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    @@ -196,9 +196,6 @@ public function setUp() {
    -    // @todo Remove this in https://www.drupal.org/node/2815845.
    -    drupal_flush_all_caches();
    

    Hurray!

  3. +++ b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
    @@ -130,6 +130,9 @@ public function setUp() {
    +    // If we remove resources we also need to ensure they don't stick in the
    +    // {router} table.
    +    \Drupal::service('router.builder')->rebuild();
    
    @@ -152,8 +155,10 @@ protected function provisionResource($resource_type, $formats = [], $authenticat
    -    // @todo Remove this in https://www.drupal.org/node/2815845.
    -    drupal_flush_all_caches();
    +    // Ensure that the cache tags invalidator has its internal values reset.
    +    // Otherwise the http_response cache tag invalidation won't work.
    +    $this->refreshVariables();
    +    \Drupal::service('router.builder')->rebuild();
    

    Let's rephrase this so that it's clear that we need to rebuild this solely for the purpose of the route building we do _inside_ the test (i.e. the tester, not the testee).

  4. +++ b/core/modules/rest/src/Tests/RESTTestBase.php
    @@ -420,8 +420,7 @@ protected function enableService($resource_type, $method = 'GET', $format = NULL
    -    // Rebuild routing cache, so that the REST API paths are available.
    -    $this->container->get('router.builder')->rebuild();
    +    $this->container->get('router.builder')->rebuildIfNeeded();
    

    This one is oddly inconsistent with the changes in ResourceTestBase. This indicates we should also be able to update ResourceTestBase to use rebuildIfNeeded(). Doing so locally shows it works.


I addressed all of my own feedback. IMO it can be RTBC'd now. But it still needs a CR (working on that now).

wim leers’s picture

dawehner’s picture

The latest interdiff totally works for me. I love new methods with a proper name.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

Cool :)

RTBC then. I've been reviewing this patch, and dawehner just reviewed the sole interdiff I posted. (#13 was a straight rebase, #15 was just removing the @todo that pointed to this issue. @dawehner did all the work.)

The last submitted patch, 41: 2815845-41.patch, failed testing.

tedbow’s picture

This patch looks good to me. +1 the RTBC, was a little take a back by the number file but most are simply adding 'http_response' to $expected_tags for test response.
Should make test run faster right? yay!

tedbow’s picture

Status: Reviewed & tested by the community » Needs work

Actually I am still seeing @todo's pointing to this issue. 4 in EntityResourceTestBase.php
Like this.

$this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE);
    // @todo Remove this in https://www.drupal.org/node/2815845.
    drupal_flush_all_caches();

The are not after saving REST resource config objects so maybe we still need the drupal_flush_all_caches() call?
But we should remove the todo's if so

+++ b/core/modules/rest/src/Entity/RestResourceConfig.php
@@ -255,4 +256,22 @@ protected function normalizeRestMethod($method) {
+  public static function postDelete(EntityStorageInterface $storage, array $entities) {
+    parent::postDelete($storage, $entities);
+
+    \Drupal::service('router.builder')->setRebuildNeeded();
+  }

The postSave changes is tested by the fact that after \Drupal\Tests\rest\Functional\ResourceTestBase::provisionResource all the routes are avaiable for the tests.

But for postDelet() what confirms the route is being rebuilt after deleting a REST resouce? Is this important?

At the end of each of the test*() methods in we could \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase delete the resource and confirm we get a 404.

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs work » Needs review
StatusFileSize
new2.08 KB
new27.3 KB

Thanks for the extra diligence, @tedbow! I can't believe I missed those additional @todos back in #15.


Modifying rest.settings' bc_entity_resource_permissions causes routes to change. The changes in RestResourceConfig::(post|pre)save trigger a route rebuild when REST Resource Config entities are modified. But when rest.settings.bc_entity_permissions is modified, we also need to trigger a route rebuild!

All the remaining @todos are about rest.settings.bc_entity_permissions.

First, let's prove that what I wrote here is actually true. This should fail: this should result in 200 responses instead of 403 responses because routes are not being rebuilt and hence the BC entity permissions are not required, which is why it's resulting in 200 responses instead of 403.

wim leers’s picture

StatusFileSize
new2.57 KB
new29.69 KB

This adds a ConfigEvents::SAVE subscriber that calls setRebuildNeeded() so that the router is rebuilt also when rest.settings.bc_entity_permissions is modified.

This should be green again.

wim leers’s picture

Assigned: wim leers » Unassigned
StatusFileSize
new3.79 KB
new29.73 KB
  1. +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    @@ -395,8 +393,7 @@ public function testGet() {
         $this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE);
    -    // @todo Remove this in https://www.drupal.org/node/2815845.
    -    drupal_flush_all_caches();
    +    $this->refreshTestStateAfterRestResourceConfigChange();
    

    The first line here is not modifying a REST Resource Config entity. It's modifying rest.settings, which is just Simple Configuration.

    Hence the name of the method called on the last line is not a great match.

  2. +++ b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
    @@ -141,8 +142,23 @@ protected function provisionResource($resource_type, $formats = [], $authenticat
    +   * Should be called after every RestResourceConfig entity change.
    +   */
    +  protected function refreshTestStateAfterRestResourceConfigChange() {
    ...
    +    // Tests using this base class may trigger route rebuilds due to changes to
    +    // RestResourceConfig entities. Ensure we generate routes using an
    +    // up-to-date router.
    

    IOW: these comments and the method name should be modified.

Addressed that.

The last submitted patch, 59: 2815845-60.patch, failed testing.

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs review » Needs work

@tedbow pointed out in chat that this is not yet addressed:

But for postDelet() what confirms the route is being rebuilt after deleting a REST resouce? Is this important?

At the end of each of the test*() methods in we could \Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase delete the resource and confirm we get a 404.

Of course he is right.

\Drupal\Tests\rest\Functional\ResourceTestBase::setUp()does this:

    // Ensure there's a clean slate: delete all REST resource config entities.
    $this->resourceConfigStorage->delete($this->resourceConfigStorage->loadMultiple());

This deletes the default REST resource config entities: the one for node. So we’re already testing it, just not explicitly.

Let's do this right.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
StatusFileSize
new1.36 KB
new30.61 KB

Done.

Status: Needs review » Needs work

The last submitted patch, 64: 2815845-64.patch, failed testing.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

I'm looking into those failures

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new30.87 KB
new1.02 KB

Here is a fix. As explained in the codecomment, we actually expect a 406.

Status: Needs review » Needs work

The last submitted patch, 68: 2815845-68.patch, failed testing.

webflo’s picture

The 403 is comes from AuthenticationSubscriber::onKernelRequestFilterProvider. The AuthenticationSubscriber runs before the route access check. @dawehner had an idea how to handle this edge for BasicAuth but i could not follow.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.08 KB
new30.68 KB

Actually, that 406 vs 403 behavior was encountered before, and there already is a helper method to deal with that. This behavior is being fixed in #2805279: Routing system + authentication system + format-specific routes (e.g. those in rest.module) = frustrating, unhelpful 403 responses instead of 406 responses.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Nice fix!

wim leers’s picture

wim leers’s picture

This is also causing problems in the REST UI contrib module. It blocks the following fixes:

  1. #2851127: REST UI module should not be rebuilding routes
  2. #2851126: The UI says "disable", but it's really "delete"
alexpott’s picture

StatusFileSize
new560 bytes
new30.47 KB

Needed a re-roll - fixed and unused use too.

wim leers’s picture

Issue summary: View changes
alexpott’s picture

Discussed with @Berdir, @swentel and @Wim Leers in IRC.

I raised a concern about invalidating all http_responses on router rebuild. @Berdir and @Wim Leers pointed out that with routes appearing and disappearing you need to invalidate these tags to ensure that users get the expected 200 or 404. If this does not occur they'll get stale content. We discussed with the RouterBuilder could be made smarter and only clear affected URLs but this would require wildcard cache clearing which is just not possible and more granular cache tags would be very expensive. Out of the discussion I asked for two followups:

  1. We should document the consequences of calling \Drupal\Core\Routing\RouteBuilderInterface::setRebuildNeeded()
  2. We should review all calls to \Drupal\Core\Routing\RouteBuilderInterface::setRebuildNeeded() to ensure they only occur when required. For example node_form_system_themes_admin_form_submit() that should be moved to a config listener and only done if the value is changed.

Unfortunately the are no easy wins here. Thanks @Wim Leers for updating the issue summary to outline the consequences of this change.

wim leers’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 172e9e3 to 8.4.x and 2dcf1bc to 8.3.x. Thanks!

  • alexpott committed 172e9e3 on 8.4.x
    Issue #2815845 by dawehner, Wim Leers, alexpott, tedbow, Berdir, swentel...
xjm’s picture

Issue tags: +8.3.0 release notes

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

quietone’s picture

publish the change record