1<?php
2/*
3 *
4 * Copyright 2018 gRPC authors.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20class ChannelTest extends PHPUnit_Framework_TestCase
21{
22    public function setUp()
23    {
24    }
25
26    public function tearDown()
27    {
28        if (!empty($this->channel)) {
29            $this->channel->close();
30        }
31    }
32
33    public function testInsecureCredentials()
34    {
35        $this->channel = new Grpc\Channel('localhost:50000',
36            ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
37        $this->assertSame('Grpc\Channel', get_class($this->channel));
38    }
39
40    public function testGetConnectivityState()
41    {
42        $this->channel = new Grpc\Channel('localhost:50001',
43             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
44        $state = $this->channel->getConnectivityState();
45        $this->assertEquals(0, $state);
46    }
47
48    public function testGetConnectivityStateWithInt()
49    {
50        $this->channel = new Grpc\Channel('localhost:50002',
51             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
52        $state = $this->channel->getConnectivityState(123);
53        $this->assertEquals(0, $state);
54    }
55
56    public function testGetConnectivityStateWithString()
57    {
58        $this->channel = new Grpc\Channel('localhost:50003',
59             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
60        $state = $this->channel->getConnectivityState('hello');
61        $this->assertEquals(0, $state);
62    }
63
64    public function testGetConnectivityStateWithBool()
65    {
66        $this->channel = new Grpc\Channel('localhost:50004',
67             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
68        $state = $this->channel->getConnectivityState(true);
69        $this->assertEquals(0, $state);
70    }
71
72    public function testGetTarget()
73    {
74        $this->channel = new Grpc\Channel('localhost:50005',
75             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
76        $target = $this->channel->getTarget();
77        $this->assertTrue(is_string($target));
78    }
79
80    public function testWatchConnectivityState()
81    {
82        $this->channel = new Grpc\Channel('localhost:50006',
83             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
84        $now = Grpc\Timeval::now();
85        $deadline = $now->add(new Grpc\Timeval(100*1000));  // 100ms
86        // we act as if 'CONNECTING'(=1) was the last state
87        // we saw, so the default state of 'IDLE' should be delivered instantly
88        $state = $this->channel->watchConnectivityState(1, $deadline);
89        $this->assertTrue($state);
90        unset($now);
91        unset($deadline);
92    }
93
94    public function testClose()
95    {
96        $this->channel = new Grpc\Channel('localhost:50007',
97             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
98        $this->assertNotNull($this->channel);
99        $this->channel->close();
100    }
101
102    /**
103     * @expectedException InvalidArgumentException
104     */
105    public function testInvalidConstructorWithNull()
106    {
107        $this->channel = new Grpc\Channel();
108        $this->assertNull($this->channel);
109    }
110
111    /**
112     * @expectedException InvalidArgumentException
113     */
114    public function testInvalidConstructorWith()
115    {
116        $this->channel = new Grpc\Channel('localhost:50008', 'invalid');
117        $this->assertNull($this->channel);
118    }
119
120    /**
121     * @expectedException InvalidArgumentException
122     */
123    public function testInvalidCredentials()
124    {
125        $this->channel = new Grpc\Channel('localhost:50009',
126            ['credentials' => new Grpc\Timeval(100)]);
127    }
128
129    /**
130     * @expectedException InvalidArgumentException
131     */
132    public function testInvalidOptionsArray()
133    {
134        $this->channel = new Grpc\Channel('localhost:50010',
135            ['abc' => []]);
136    }
137
138    /**
139     * @expectedException InvalidArgumentException
140     */
141    public function testInvalidGetConnectivityStateWithArray()
142    {
143        $this->channel = new Grpc\Channel('localhost:50011',
144            ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
145        $this->channel->getConnectivityState([]);
146    }
147
148    /**
149     * @expectedException InvalidArgumentException
150     */
151    public function testInvalidWatchConnectivityState()
152    {
153        $this->channel = new Grpc\Channel('localhost:50012',
154            ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
155        $this->channel->watchConnectivityState([]);
156    }
157
158    /**
159     * @expectedException InvalidArgumentException
160     */
161    public function testInvalidWatchConnectivityState2()
162    {
163        $this->channel = new Grpc\Channel('localhost:50013',
164            ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
165        $this->channel->watchConnectivityState(1, 'hi');
166    }
167
168
169    public function assertConnecting($state) {
170      $this->assertTrue($state == GRPC\CHANNEL_CONNECTING ||
171                        $state == GRPC\CHANNEL_TRANSIENT_FAILURE);
172    }
173
174    public function waitUntilNotIdle($channel) {
175        for ($i = 0; $i < 10; $i++) {
176            $now = Grpc\Timeval::now();
177            $deadline = $now->add(new Grpc\Timeval(1000));
178            if ($channel->watchConnectivityState(GRPC\CHANNEL_IDLE,
179                                                 $deadline)) {
180                return true;
181            }
182        }
183        $this->assertTrue(false);
184    }
185
186    public function testPersistentChannelSameHost()
187    {
188        $this->channel1 = new Grpc\Channel('localhost:50014', [
189            "grpc_target_persist_bound" => 3,
190        ]);
191        // the underlying grpc channel is the same by default
192        // when connecting to the same host
193        $this->channel2 = new Grpc\Channel('localhost:50014', []);
194
195        // both channels should be IDLE
196        $state = $this->channel1->getConnectivityState();
197        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
198        $state = $this->channel2->getConnectivityState();
199        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
200
201        // try to connect on channel1
202        $state = $this->channel1->getConnectivityState(true);
203        $this->waitUntilNotIdle($this->channel1);
204
205        // both channels should now be in the CONNECTING state
206        $state = $this->channel1->getConnectivityState();
207        $this->assertConnecting($state);
208        $state = $this->channel2->getConnectivityState();
209        $this->assertConnecting($state);
210
211        $this->channel1->close();
212        $this->channel2->close();
213    }
214
215    public function testPersistentChannelDifferentHost()
216    {
217        // two different underlying channels because different hostname
218        $this->channel1 = new Grpc\Channel('localhost:50015', [
219            "grpc_target_persist_bound" => 3,
220        ]);
221        $this->channel2 = new Grpc\Channel('localhost:50016', []);
222
223        // both channels should be IDLE
224        $state = $this->channel1->getConnectivityState();
225        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
226        $state = $this->channel2->getConnectivityState();
227        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
228
229        // try to connect on channel1
230        $state = $this->channel1->getConnectivityState(true);
231        $this->waitUntilNotIdle($this->channel1);
232
233        // channel1 should now be in the CONNECTING state
234        $state = $this->channel1->getConnectivityState();
235        $this->assertConnecting($state);
236        // channel2 should still be in the IDLE state
237        $state = $this->channel2->getConnectivityState();
238        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
239
240        $this->channel1->close();
241        $this->channel2->close();
242    }
243
244    public function testPersistentChannelSameArgs()
245    {
246        $this->channel1 = new Grpc\Channel('localhost:50017', [
247          "grpc_target_persist_bound" => 3,
248          "abc" => "def",
249          ]);
250        $this->channel2 = new Grpc\Channel('localhost:50017', ["abc" => "def"]);
251
252        // try to connect on channel1
253        $state = $this->channel1->getConnectivityState(true);
254        $this->waitUntilNotIdle($this->channel1);
255
256        $state = $this->channel1->getConnectivityState();
257        $this->assertConnecting($state);
258        $state = $this->channel2->getConnectivityState();
259        $this->assertConnecting($state);
260
261        $this->channel1->close();
262        $this->channel2->close();
263    }
264
265    public function testPersistentChannelDifferentArgs()
266    {
267        $this->channel1 = new Grpc\Channel('localhost:50018', [
268            "grpc_target_persist_bound" => 3,
269          ]);
270        $this->channel2 = new Grpc\Channel('localhost:50018', ["abc" => "def"]);
271
272        // try to connect on channel1
273        $state = $this->channel1->getConnectivityState(true);
274        $this->waitUntilNotIdle($this->channel1);
275
276        $state = $this->channel1->getConnectivityState();
277        $this->assertConnecting($state);
278        $state = $this->channel2->getConnectivityState();
279        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
280
281        $this->channel1->close();
282        $this->channel2->close();
283    }
284
285    public function testPersistentChannelSameChannelCredentials()
286    {
287        $creds1 = Grpc\ChannelCredentials::createSsl();
288        $creds2 = Grpc\ChannelCredentials::createSsl();
289
290        $this->channel1 = new Grpc\Channel('localhost:50019',
291                                           ["credentials" => $creds1,
292                                             "grpc_target_persist_bound" => 3,
293                                             ]);
294        $this->channel2 = new Grpc\Channel('localhost:50019',
295                                           ["credentials" => $creds2]);
296
297        // try to connect on channel1
298        $state = $this->channel1->getConnectivityState(true);
299        $this->waitUntilNotIdle($this->channel1);
300
301        $state = $this->channel1->getConnectivityState();
302        $this->assertConnecting($state);
303        $state = $this->channel2->getConnectivityState();
304        $this->assertConnecting($state);
305
306        $this->channel1->close();
307        $this->channel2->close();
308    }
309
310    public function testPersistentChannelDifferentChannelCredentials()
311    {
312        $creds1 = Grpc\ChannelCredentials::createSsl();
313        $creds2 = Grpc\ChannelCredentials::createSsl(
314            file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
315
316        $this->channel1 = new Grpc\Channel('localhost:50020',
317                                           ["credentials" => $creds1,
318                                             "grpc_target_persist_bound" => 3,
319                                             ]);
320        $this->channel2 = new Grpc\Channel('localhost:50020',
321                                           ["credentials" => $creds2]);
322
323        // try to connect on channel1
324        $state = $this->channel1->getConnectivityState(true);
325        $this->waitUntilNotIdle($this->channel1);
326
327        $state = $this->channel1->getConnectivityState();
328        $this->assertConnecting($state);
329        $state = $this->channel2->getConnectivityState();
330        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
331
332        $this->channel1->close();
333        $this->channel2->close();
334    }
335
336    public function testPersistentChannelSameChannelCredentialsRootCerts()
337    {
338        $creds1 = Grpc\ChannelCredentials::createSsl(
339            file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
340        $creds2 = Grpc\ChannelCredentials::createSsl(
341            file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
342
343        $this->channel1 = new Grpc\Channel('localhost:50021',
344                                           ["credentials" => $creds1,
345                                             "grpc_target_persist_bound" => 3,
346                                             ]);
347        $this->channel2 = new Grpc\Channel('localhost:50021',
348                                           ["credentials" => $creds2]);
349
350        // try to connect on channel1
351        $state = $this->channel1->getConnectivityState(true);
352        $this->waitUntilNotIdle($this->channel1);
353
354        $state = $this->channel1->getConnectivityState();
355        $this->assertConnecting($state);
356        $state = $this->channel2->getConnectivityState();
357        $this->assertConnecting($state);
358
359        $this->channel1->close();
360        $this->channel2->close();
361    }
362
363    public function testPersistentChannelDifferentSecureChannelCredentials()
364    {
365        $creds1 = Grpc\ChannelCredentials::createSsl();
366        $creds2 = Grpc\ChannelCredentials::createInsecure();
367
368        $this->channel1 = new Grpc\Channel('localhost:50022',
369                                           ["credentials" => $creds1,
370                                             "grpc_target_persist_bound" => 3,
371                                             ]);
372        $this->channel2 = new Grpc\Channel('localhost:50022',
373                                           ["credentials" => $creds2]);
374
375        // try to connect on channel1
376        $state = $this->channel1->getConnectivityState(true);
377        $this->waitUntilNotIdle($this->channel1);
378
379        $state = $this->channel1->getConnectivityState();
380        $this->assertConnecting($state);
381        $state = $this->channel2->getConnectivityState();
382        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
383
384        $this->channel1->close();
385        $this->channel2->close();
386    }
387
388    public function testPersistentChannelSharedChannelClose1()
389    {
390        // same underlying channel
391        $this->channel1 = new Grpc\Channel('localhost:50123', [
392            "grpc_target_persist_bound" => 3,
393        ]);
394        $this->channel2 = new Grpc\Channel('localhost:50123', []);
395
396        // close channel1
397        $this->channel1->close();
398
399        // channel2 can still be use. We need to exclude the possible that
400        // in testPersistentChannelSharedChannelClose2, the exception is thrown
401        // by channel1.
402        $state = $this->channel2->getConnectivityState();
403        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
404    }
405
406    /**
407     * @expectedException RuntimeException
408     */
409    public function testPersistentChannelSharedChannelClose2()
410    {
411        // same underlying channel
412        $this->channel1 = new Grpc\Channel('localhost:50223', [
413            "grpc_target_persist_bound" => 3,
414        ]);
415        $this->channel2 = new Grpc\Channel('localhost:50223', []);
416
417        // close channel1
418        $this->channel1->close();
419
420        // channel2 can still be use
421        $state = $this->channel2->getConnectivityState();
422        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
423
424        // channel 1 is closed
425        $state = $this->channel1->getConnectivityState();
426    }
427
428    public function testPersistentChannelCreateAfterClose()
429    {
430        $this->channel1 = new Grpc\Channel('localhost:50024', [
431            "grpc_target_persist_bound" => 3,
432        ]);
433
434        $this->channel1->close();
435
436        $this->channel2 = new Grpc\Channel('localhost:50024', []);
437        $state = $this->channel2->getConnectivityState();
438        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
439
440        $this->channel2->close();
441    }
442
443    public function testPersistentChannelSharedMoreThanTwo()
444    {
445        $this->channel1 = new Grpc\Channel('localhost:50025', [
446            "grpc_target_persist_bound" => 3,
447        ]);
448        $this->channel2 = new Grpc\Channel('localhost:50025', []);
449        $this->channel3 = new Grpc\Channel('localhost:50025', []);
450
451        // try to connect on channel1
452        $state = $this->channel1->getConnectivityState(true);
453        $this->waitUntilNotIdle($this->channel1);
454
455        // all 3 channels should be in CONNECTING state
456        $state = $this->channel1->getConnectivityState();
457        $this->assertConnecting($state);
458        $state = $this->channel2->getConnectivityState();
459        $this->assertConnecting($state);
460        $state = $this->channel3->getConnectivityState();
461        $this->assertConnecting($state);
462
463        $this->channel1->close();
464    }
465
466    public function callbackFunc($context)
467    {
468        return [];
469    }
470
471    public function callbackFunc2($context)
472    {
473        return ["k1" => "v1"];
474    }
475
476    public function testPersistentChannelWithCallCredentials()
477    {
478        $creds = Grpc\ChannelCredentials::createSsl();
479        $callCreds = Grpc\CallCredentials::createFromPlugin(
480            [$this, 'callbackFunc']);
481        $credsWithCallCreds = Grpc\ChannelCredentials::createComposite(
482            $creds, $callCreds);
483
484        // If a ChannelCredentials object is composed with a
485        // CallCredentials object, the underlying grpc channel will
486        // always be created new and NOT persisted.
487        $this->channel1 = new Grpc\Channel('localhost:50026',
488                                           ["credentials" =>
489                                            $credsWithCallCreds,
490                                            "grpc_target_persist_bound" => 3,
491                                            ]);
492        $this->channel2 = new Grpc\Channel('localhost:50026',
493                                           ["credentials" =>
494                                            $credsWithCallCreds]);
495
496        // try to connect on channel1
497        $state = $this->channel1->getConnectivityState(true);
498        $this->waitUntilNotIdle($this->channel1);
499
500        $state = $this->channel1->getConnectivityState();
501        $this->assertConnecting($state);
502        $state = $this->channel2->getConnectivityState();
503        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
504
505        $this->channel1->close();
506        $this->channel2->close();
507    }
508
509    public function testPersistentChannelWithDifferentCallCredentials()
510    {
511        $callCreds1 = Grpc\CallCredentials::createFromPlugin(
512            [$this, 'callbackFunc']);
513        $callCreds2 = Grpc\CallCredentials::createFromPlugin(
514            [$this, 'callbackFunc2']);
515
516        $creds1 = Grpc\ChannelCredentials::createSsl();
517        $creds2 = Grpc\ChannelCredentials::createComposite(
518            $creds1, $callCreds1);
519        $creds3 = Grpc\ChannelCredentials::createComposite(
520            $creds1, $callCreds2);
521
522        // Similar to the test above, anytime a ChannelCredentials
523        // object is composed with a CallCredentials object, the
524        // underlying grpc channel will always be separate and not
525        // persisted
526        $this->channel1 = new Grpc\Channel('localhost:50027',
527                                           ["credentials" => $creds1,
528                                            "grpc_target_persist_bound" => 3,
529                                            ]);
530        $this->channel2 = new Grpc\Channel('localhost:50027',
531                                           ["credentials" => $creds2]);
532        $this->channel3 = new Grpc\Channel('localhost:50027',
533                                           ["credentials" => $creds3]);
534
535        // try to connect on channel1
536        $state = $this->channel1->getConnectivityState(true);
537        $this->waitUntilNotIdle($this->channel1);
538
539        $state = $this->channel1->getConnectivityState();
540        $this->assertConnecting($state);
541        $state = $this->channel2->getConnectivityState();
542        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
543        $state = $this->channel3->getConnectivityState();
544        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
545
546        $this->channel1->close();
547        $this->channel2->close();
548        $this->channel3->close();
549    }
550
551    public function testPersistentChannelForceNew()
552    {
553        $this->channel1 = new Grpc\Channel('localhost:50028', [
554            "grpc_target_persist_bound" => 2,
555        ]);
556        // even though all the channel params are the same, channel2
557        // has a new and different underlying channel
558        $this->channel2 = new Grpc\Channel('localhost:50028',
559                                           ["force_new" => true]);
560
561        // try to connect on channel1
562        $state = $this->channel1->getConnectivityState(true);
563        $this->waitUntilNotIdle($this->channel1);
564
565        $state = $this->channel1->getConnectivityState();
566        $this->assertConnecting($state);
567        $state = $this->channel2->getConnectivityState();
568        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
569
570        $this->channel1->close();
571        $this->channel2->close();
572    }
573
574    public function testPersistentChannelForceNewOldChannelIdle1()
575    {
576
577        $this->channel1 = new Grpc\Channel('localhost:50029', [
578            "grpc_target_persist_bound" => 2,
579        ]);
580        $this->channel2 = new Grpc\Channel('localhost:50029',
581                                           ["force_new" => true]);
582        // channel3 shares with channel1
583        $this->channel3 = new Grpc\Channel('localhost:50029', []);
584
585        // try to connect on channel2
586        $state = $this->channel2->getConnectivityState(true);
587        $this->waitUntilNotIdle($this->channel2);
588        $state = $this->channel1->getConnectivityState();
589        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
590        $state = $this->channel2->getConnectivityState();
591        $this->assertConnecting($state);
592        $state = $this->channel3->getConnectivityState();
593        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
594
595        $this->channel1->close();
596        $this->channel2->close();
597    }
598
599    public function testPersistentChannelForceNewOldChannelIdle2()
600    {
601
602        $this->channel1 = new Grpc\Channel('localhost:50029', [
603            "grpc_target_persist_bound" => 2,
604        ]);
605        $this->channel2 = new Grpc\Channel('localhost:50029', []);
606
607        // try to connect on channel2
608        $state = $this->channel1->getConnectivityState(true);
609        $this->waitUntilNotIdle($this->channel2);
610        $state = $this->channel1->getConnectivityState();
611        $this->assertConnecting($state);
612        $state = $this->channel2->getConnectivityState();
613        $this->assertConnecting($state);
614
615        $this->channel1->close();
616        $this->channel2->close();
617    }
618
619    public function testPersistentChannelForceNewOldChannelClose1()
620    {
621
622        $this->channel1 = new Grpc\Channel('localhost:50130', [
623            "grpc_target_persist_bound" => 2,
624        ]);
625        $this->channel2 = new Grpc\Channel('localhost:50130',
626                                           ["force_new" => true]);
627        // channel3 shares with channel1
628        $this->channel3 = new Grpc\Channel('localhost:50130', []);
629
630        $this->channel1->close();
631
632        $state = $this->channel2->getConnectivityState();
633        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
634
635        // channel3 is still usable. We need to exclude the possibility that in
636        // testPersistentChannelForceNewOldChannelClose2, the exception is thrown
637        // by channel1 and channel2.
638        $state = $this->channel3->getConnectivityState();
639        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
640    }
641
642    /**
643     * @expectedException RuntimeException
644     */
645    public function testPersistentChannelForceNewOldChannelClose2()
646    {
647
648        $this->channel1 = new Grpc\Channel('localhost:50230', [
649            "grpc_target_persist_bound" => 2,
650        ]);
651        $this->channel2 = new Grpc\Channel('localhost:50230',
652          ["force_new" => true]);
653        // channel3 shares with channel1
654        $this->channel3 = new Grpc\Channel('localhost:50230', []);
655
656        $this->channel1->close();
657
658        $state = $this->channel2->getConnectivityState();
659        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
660
661        // channel3 is still usable
662        $state = $this->channel3->getConnectivityState();
663        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
664
665        // channel 1 is closed
666        $this->channel1->getConnectivityState();
667    }
668
669    public function testPersistentChannelForceNewNewChannelClose()
670    {
671
672        $this->channel1 = new Grpc\Channel('localhost:50031', [
673            "grpc_target_persist_bound" => 2,
674        ]);
675        $this->channel2 = new Grpc\Channel('localhost:50031',
676                                           ["force_new" => true]);
677        $this->channel3 = new Grpc\Channel('localhost:50031', []);
678
679        $this->channel2->close();
680
681        $state = $this->channel1->getConnectivityState();
682        $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
683
684        // can still connect on channel1
685        $state = $this->channel1->getConnectivityState(true);
686        $this->waitUntilNotIdle($this->channel1);
687
688        $state = $this->channel1->getConnectivityState();
689        $this->assertConnecting($state);
690
691        $this->channel1->close();
692    }
693}
694