Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC") 

# 

# Permission to use, copy, modify, and distribute this software for any 

# purpose with or without fee is hereby granted, provided that the above 

# copyright notice and this permission notice appear in all copies. 

# 

# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM 

# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 

# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 

# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, 

# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING 

# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 

# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 

# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 

 

import unittest 

import isc.log 

import isc.bind10.socket_cache 

import isc.bind10.sockcreator 

from isc.net.addr import IPAddr 

import os 

 

class Test(unittest.TestCase): 

    """ 

    Base for the tests here. It replaces the os.close method. 

    """ 

    def setUp(self): 

        self._closes = [] 

        isc.bind10.socket_cache.os.close = self.__close 

 

    def tearDown(self): 

        # This is not very clean solution. But when the test stops 

        # to exist, the method must not be used to destroy the 

        # object any more. And we can't restore the os.close here 

        # as we never work with real sockets here. 

exit        isc.bind10.socket_cache.os.close = lambda fd: None 

 

    def __close(self, fd): 

        """ 

        Just log a close was called. 

        """ 

        self._closes.append(fd) 

 

class SocketTest(Test): 

    """ 

    Test for the Socket class. 

    """ 

    def setUp(self): 

        """ 

        Creates the socket to be tested. 

 

        It also creates other useful test variables. 

        """ 

        Test.setUp(self) 

        self.__address = IPAddr("192.0.2.1") 

        self.__socket = isc.bind10.socket_cache.Socket('UDP', self.__address, 

                                                       1024, 42) 

 

    def test_init(self): 

        """ 

        Checks the intrnals of the cache just after the creation. 

        """ 

        self.assertEqual('UDP', self.__socket.protocol) 

        self.assertEqual(self.__address, self.__socket.address) 

        self.assertEqual(1024, self.__socket.port) 

        self.assertEqual(42, self.__socket.fileno) 

        self.assertEqual({}, self.__socket.active_tokens) 

        self.assertEqual({}, self.__socket.shares) 

        self.assertEqual(set(), self.__socket.waiting_tokens) 

 

    def test_del(self): 

        """ 

        Check it closes the socket when removed. 

        """ 

        # This should make the refcount 0 and call the descructor 

        # right away 

        self.__socket = None 

        self.assertEqual([42], self._closes) 

 

    def test_share_modes(self): 

        """ 

        Test the share mode compatibility check function. 

        """ 

        modes = ['NO', 'SAMEAPP', 'ANY'] 

        # If there are no shares, it is compatible with everything. 

        for mode in modes: 

            self.assertTrue(self.__socket.share_compatible(mode, 'anything')) 

 

        # There's an NO already, so it is incompatible with everything. 

        self.__socket.shares = {'token': ('NO', 'anything')} 

        for mode in modes: 

            self.assertFalse(self.__socket.share_compatible(mode, 'anything')) 

 

        # If there's SAMEAPP, it is compatible with ANY and SAMEAPP with the 

        # same name. 

        self.__socket.shares = {'token': ('SAMEAPP', 'app')} 

        self.assertFalse(self.__socket.share_compatible('NO', 'app')) 

        self.assertFalse(self.__socket.share_compatible('SAMEAPP', 

                                                        'something')) 

        self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app')) 

        self.assertTrue(self.__socket.share_compatible('ANY', 'app')) 

        self.assertFalse(self.__socket.share_compatible('ANY', 'something')) 

 

        # If there's ANY, then ANY and SAMEAPP with the same name is compatible 

        self.__socket.shares = {'token': ('ANY', 'app')} 

        self.assertFalse(self.__socket.share_compatible('NO', 'app')) 

        self.assertFalse(self.__socket.share_compatible('SAMEAPP', 

                                                        'something')) 

        self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app')) 

        self.assertTrue(self.__socket.share_compatible('ANY', 'something')) 

 

        # In case there are multiple already inside 

        self.__socket.shares = { 

            'token': ('ANY', 'app'), 

            'another': ('SAMEAPP', 'app') 

        } 

        self.assertFalse(self.__socket.share_compatible('NO', 'app')) 

        self.assertFalse(self.__socket.share_compatible('SAMEAPP', 

                                                        'something')) 

        self.assertTrue(self.__socket.share_compatible('SAMEAPP', 'app')) 

        self.assertFalse(self.__socket.share_compatible('ANY', 'something')) 

        self.assertTrue(self.__socket.share_compatible('ANY', 'app')) 

 

        # Invalid inputs are rejected 

        self.assertRaises(ValueError, self.__socket.share_compatible, 'bad', 

                          'bad') 

 

class SocketCacheTest(Test): 

    """ 

    Some tests for the isc.bind10.socket_cache.Cache. 

 

    This class, as well as being the testcase, pretends to be the 

    socket creator so it can hijack all the requests for sockets. 

    """ 

    def setUp(self): 

        """ 

        Creates the cache for tests with us being the socket creator. 

 

        Also creates some more variables for testing. 

        """ 

        Test.setUp(self) 

        self.__cache = isc.bind10.socket_cache.Cache(self) 

        self.__address = IPAddr("192.0.2.1") 

        self.__socket = isc.bind10.socket_cache.Socket('UDP', self.__address, 

                                                       1024, 42) 

        self.__get_socket_called = False 

 

    def test_init(self): 

        """ 

        Checks the internals of the cache just after the creation. 

        """ 

        self.assertEqual(self, self.__cache._creator) 

        self.assertEqual({}, self.__cache._waiting_tokens) 

        self.assertEqual({}, self.__cache._active_tokens) 

        self.assertEqual({}, self.__cache._active_apps) 

        self.assertEqual({}, self.__cache._sockets) 

        self.assertEqual(set(), self.__cache._live_tokens) 

 

    def get_socket(self, address, port, socktype): 

        """ 

        Pretend to be a socket creator. 

 

        This expects to be called with the _address, port 1024 and 'UDP'. 

 

        Returns 42 and notes down it was called. 

        """ 

        self.assertEqual(self.__address, address) 

        self.assertEqual(1024, port) 

        self.assertEqual('UDP', socktype) 

        self.__get_socket_called = True 

        return 42 

 

    def test_get_token_cached(self): 

        """ 

        Check the behaviour of get_token when the requested socket is already 

        cached inside. 

        """ 

        self.__cache._sockets = { 

            'UDP': {'192.0.2.1': {1024: self.__socket}} 

        } 

        token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY', 

                                       'test') 

        # It didn't call get_socket 

        self.assertFalse(self.__get_socket_called) 

        # It returned something 

        self.assertIsNotNone(token) 

        # The token is both in the waiting sockets and the live tokens 

        self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens) 

        self.assertEqual(set([token]), self.__cache._live_tokens) 

        # The token got the new share to block any relevant queries 

        self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares) 

        # The socket knows the token is waiting in it 

        self.assertEqual(set([token]), self.__socket.waiting_tokens) 

 

        # If we request one more, with incompatible share, it is rejected 

        self.assertRaises(isc.bind10.socket_cache.ShareError, 

                          self.__cache.get_token, 'UDP', self.__address, 1024, 

                          'NO', 'test') 

        # The internals are not changed, so the same checks 

        self.assertEqual({token: self.__socket}, self.__cache._waiting_tokens) 

        self.assertEqual(set([token]), self.__cache._live_tokens) 

        self.assertEqual({token: ('ANY', 'test')}, self.__socket.shares) 

        self.assertEqual(set([token]), self.__socket.waiting_tokens) 

 

    def test_get_token_uncached(self): 

        """ 

        Check a new socket is created when a corresponding one is missing. 

        """ 

        token = self.__cache.get_token('UDP', self.__address, 1024, 'ANY', 

                                       'test') 

        # The get_socket was called 

        self.assertTrue(self.__get_socket_called) 

        # It returned something 

        self.assertIsNotNone(token) 

        # Get the socket and check it looks OK 

        socket = self.__cache._waiting_tokens[token] 

        self.assertEqual(self.__address, socket.address) 

        self.assertEqual(1024, socket.port) 

        self.assertEqual(42, socket.fileno) 

        self.assertEqual('UDP', socket.protocol) 

        # The socket is properly cached 

        self.assertEqual({ 

            'UDP': {'192.0.2.1': {1024: socket}} 

        }, self.__cache._sockets) 

        # The token is both in the waiting sockets and the live tokens 

        self.assertEqual({token: socket}, self.__cache._waiting_tokens) 

        self.assertEqual(set([token]), self.__cache._live_tokens) 

        # The token got the new share to block any relevant queries 

        self.assertEqual({token: ('ANY', 'test')}, socket.shares) 

        # The socket knows the token is waiting in it 

        self.assertEqual(set([token]), socket.waiting_tokens) 

 

    def test_get_token_excs(self): 

        """ 

        Test that it is handled properly if the socket creator raises 

        some exceptions. 

        """ 

        def raiseCreatorError(fatal): 

            raise isc.bind10.sockcreator.CreatorError('test error', fatal) 

        # First, fatal socket creator errors are passed through 

        self.get_socket = lambda addr, port, proto: raiseCreatorError(True) 

        self.assertRaises(isc.bind10.sockcreator.CreatorError, 

                          self.__cache.get_token, 'UDP', self.__address, 1024, 

                          'NO', 'test') 

        # And nonfatal are converted to SocketError 

        self.get_socket = lambda addr, port, proto: raiseCreatorError(False) 

        self.assertRaises(isc.bind10.socket_cache.SocketError, 

                          self.__cache.get_token, 'UDP', self.__address, 1024, 

                          'NO', 'test') 

 

    def test_get_socket(self): 

        """ 

        Test that we can pickup a socket if we know a token. 

        """ 

        token = "token" 

        app = 13 

        # No socket prepared there 

        self.assertRaises(ValueError, self.__cache.get_socket, token, app) 

        # Not changed 

        self.assertEqual({}, self.__cache._active_tokens) 

        self.assertEqual({}, self.__cache._active_apps) 

        self.assertEqual({}, self.__cache._sockets) 

        self.assertEqual(set(), self.__cache._live_tokens) 

        # Prepare a token there 

        self.__socket.waiting_tokens = set([token]) 

        self.__socket.shares = {token: ('ANY', 'app')} 

        self.__cache._waiting_tokens = {token: self.__socket} 

        self.__cache._sockets = {'UDP': {'192.0.2.1': {1024: self.__socket}}} 

        self.__cache._live_tokens = set([token]) 

        socket = self.__cache.get_socket(token, app) 

        # Received the fileno 

        self.assertEqual(42, socket) 

        # It moved from waiting to active ones 

        self.assertEqual({}, self.__cache._waiting_tokens) 

        self.assertEqual({token: self.__socket}, self.__cache._active_tokens) 

        self.assertEqual({13: set([token])}, self.__cache._active_apps) 

        self.assertEqual(set([token]), self.__cache._live_tokens) 

        self.assertEqual(set(), self.__socket.waiting_tokens) 

        self.assertEqual({token: 13}, self.__socket.active_tokens) 

        # Trying to get it again fails 

        self.assertRaises(ValueError, self.__cache.get_socket, token, app) 

 

    def test_drop_application(self): 

        """ 

        Test that a drop_application calls drop_socket on all the sockets 

        held by the application. 

        """ 

        sockets = set() 

        def drop_socket(token): 

            sockets.add(token) 

        # Mock the drop_socket so we know it is called 

        self.__cache.drop_socket = drop_socket 

        self.assertRaises(ValueError, self.__cache.drop_application, 

                          13) 

        self.assertEqual(set(), sockets) 

        # Put the tokens into active_apps. Nothing else should be touched 

        # by this call, so leave it alone. 

        self.__cache._active_apps = { 

            1: set(['t1', 't2']), 

            2: set(['t3']) 

        } 

        self.__cache.drop_application(1) 

        # We don't check the _active_apps, as it would be cleaned by 

        # drop_socket and we removed it. 

        self.assertEqual(set(['t1', 't2']), sockets) 

 

    def test_drop_socket(self): 

        """ 

        Test the drop_socket call. It tests: 

        * That a socket that still has something to keep it alive is left alive 

          (both waiting and active). 

        * If not, it is deleted. 

        * All bookkeeping data around are properly removed. 

        * Of course the exception. 

        """ 

        self.assertRaises(ValueError, self.__cache.drop_socket, "bad token") 

        self.__socket.active_tokens = {'t1': 1} 

        self.__socket.waiting_tokens = set(['t2']) 

        self.__socket.shares = {'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')} 

        self.__cache._waiting_tokens = {'t2': self.__socket} 

        self.__cache._active_tokens = {'t1': self.__socket} 

        self.__cache._sockets = {'UDP': {'192.0.2.1': {1024: self.__socket}}} 

        self.__cache._live_tokens = set(['t1', 't2']) 

        self.__cache._active_apps = {1: set(['t1'])} 

        # We can't drop what wasn't picket up yet 

        self.assertRaises(ValueError, self.__cache.drop_socket, 't2') 

        self.assertEqual({'t1': 1}, self.__socket.active_tokens) 

        self.assertEqual(set(['t2']), self.__socket.waiting_tokens) 

        self.assertEqual({'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')}, 

                         self.__socket.shares) 

        self.assertEqual({'t2': self.__socket}, self.__cache._waiting_tokens) 

        self.assertEqual({'t1': self.__socket}, self.__cache._active_tokens) 

        self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}}, 

                         self.__cache._sockets) 

        self.assertEqual(set(['t1', 't2']), self.__cache._live_tokens) 

        self.assertEqual({1: set(['t1'])}, self.__cache._active_apps) 

        self.assertEqual([], self._closes) 

        # If we drop this, it survives because it waits for being picked up 

        self.__cache.drop_socket('t1') 

        self.assertEqual({}, self.__socket.active_tokens) 

        self.assertEqual(set(['t2']), self.__socket.waiting_tokens) 

        self.assertEqual({'t2': ('ANY', 'app2')}, self.__socket.shares) 

        self.assertEqual({}, self.__cache._active_tokens) 

        self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}}, 

                         self.__cache._sockets) 

        self.assertEqual(set(['t2']), self.__cache._live_tokens) 

        self.assertEqual({}, self.__cache._active_apps) 

        self.assertEqual([], self._closes) 

        # Fill it again, now two applications having the same socket 

        self.__socket.active_tokens = {'t1': 1, 't2': 2} 

        self.__socket.waiting_tokens = set() 

        self.__socket.shares = {'t1': ('ANY', 'app1'), 't2': ('ANY', 'app2')} 

        self.__cache._waiting_tokens = {} 

        self.__cache._active_tokens = { 

            't1': self.__socket, 

            't2': self.__socket 

        } 

        self.__cache._live_tokens = set(['t1', 't2', 't3']) 

        self.assertEqual([], self._closes) 

        # We cheat here little bit, the t3 doesn't exist enywhere else, but 

        # we need to check the app isn't removed too soon and it shouldn't 

        # matter anywhere else, so we just avoid the tiresome filling in 

        self.__cache._active_apps = {1: set(['t1', 't3']), 2: set(['t2'])} 

        # Drop it as t1. It should still live. 

        self.__cache.drop_socket('t1') 

        self.assertEqual({'t2': 2}, self.__socket.active_tokens) 

        self.assertEqual(set(), self.__socket.waiting_tokens) 

        self.assertEqual({'t2': ('ANY', 'app2')}, self.__socket.shares) 

        self.assertEqual({}, self.__cache._waiting_tokens) 

        self.assertEqual({'t2': self.__socket}, self.__cache._active_tokens) 

        self.assertEqual({'UDP': {'192.0.2.1': {1024: self.__socket}}}, 

                         self.__cache._sockets) 

        self.assertEqual(set(['t3', 't2']), self.__cache._live_tokens) 

        self.assertEqual({1: set(['t3']), 2: set(['t2'])}, 

                         self.__cache._active_apps) 

        self.assertEqual([], self._closes) 

        # Drop it again, from the other application. It should get removed 

        # and closed. 

        self.__cache.drop_socket('t2') 

        self.assertEqual({}, self.__socket.active_tokens) 

        self.assertEqual(set(), self.__socket.waiting_tokens) 

        self.assertEqual({}, self.__socket.shares) 

        self.assertEqual({}, self.__cache._waiting_tokens) 

        self.assertEqual({}, self.__cache._active_tokens) 

        self.assertEqual({}, self.__cache._sockets) 

        self.assertEqual(set(['t3']), self.__cache._live_tokens) 

        self.assertEqual({1: set(['t3'])}, self.__cache._active_apps) 

        # The cache doesn't hold the socket. So when we remove it ourself, 

        # it should get closed. 

        self.__socket = None 

        self.assertEqual([42], self._closes) 

 

exitif __name__ == '__main__': 

    isc.log.init("bind10") 

    isc.log.resetUnitTestRootLogger() 

    unittest.main()