[RIFT 16251] tailf yang change for confd to send the right copy-jobs status.
[osm/SO.git] / rwlaunchpad / test / utest_rwmonitor.py
1 #!/usr/bin/env python3
2
3 #
4 # Copyright 2016 RIFT.IO Inc
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
20 import argparse
21 import asyncio
22 import concurrent.futures
23 import logging
24 import os
25 import sys
26 import time
27 import unittest
28 import uuid
29 import xmlrunner
30
31 import gi
32 gi.require_version('NsrYang', '1.0')
33 gi.require_version('RwcalYang', '1.0')
34 gi.require_version('RwmonYang', '1.0')
35 gi.require_version('RwVnfrYang', '1.0')
36 gi.require_version('RwTypes', '1.0')
37 gi.require_version('RwMon', '1.0')
38
39 from gi.repository import (
40 NsrYang,
41 RwTypes,
42 RwVnfrYang,
43 RwcalYang,
44 RwmonYang,
45 VnfrYang,
46 )
47
48 from rift.tasklets.rwmonitor.core import (
49 AccountAlreadyRegisteredError,
50 AccountInUseError,
51 InstanceConfiguration,
52 Monitor,
53 NfviInterface,
54 NfviMetrics,
55 NfviMetricsCache,
56 NfviMetricsPluginManager,
57 PluginFactory,
58 PluginNotSupportedError,
59 PluginUnavailableError,
60 UnknownAccountError,
61 )
62 import rw_peas
63
64
65 class wait_for_pending_tasks(object):
66 """
67 This class defines a decorator that can be used to ensure that any asyncio
68 tasks created as a side-effect of coroutine are allowed to come to
69 completion.
70 """
71
72 def __init__(self, loop, timeout=1):
73 self.loop = loop
74 self.timeout = timeout
75
76 def __call__(self, coro):
77 @asyncio.coroutine
78 def impl():
79 original = self.pending_tasks()
80 result = yield from coro()
81
82 current = self.pending_tasks()
83 remaining = current - original
84
85 if remaining:
86 yield from asyncio.wait(
87 remaining,
88 timeout=self.timeout,
89 loop=self.loop,
90 )
91
92 return result
93
94 return impl
95
96 def pending_tasks(self):
97 return {t for t in asyncio.Task.all_tasks(loop=self.loop) if not t.done()}
98
99
100 class MockTasklet(object):
101 def __init__(self, dts, log, loop, records):
102 self.dts = dts
103 self.log = log
104 self.loop = loop
105 self.records = records
106 self.polling_period = 0
107 self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=16)
108
109
110 def make_nsr(ns_instance_config_ref=str(uuid.uuid4())):
111 nsr = NsrYang.YangData_Nsr_NsInstanceOpdata_Nsr()
112 nsr.ns_instance_config_ref = ns_instance_config_ref
113 return nsr
114
115 def make_vnfr(id=str(uuid.uuid4())):
116 vnfr = VnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr()
117 vnfr.id = id
118 return vnfr
119
120 def make_vdur(id=str(uuid.uuid4()), vim_id=str(uuid.uuid4())):
121 vdur = VnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr_Vdur()
122 vdur.id = id
123 vdur.vim_id = vim_id
124 return vdur
125
126
127 class TestNfviMetricsCache(unittest.TestCase):
128 class Plugin(object):
129 def nfvi_metrics_available(self, cloud_account):
130 return True
131
132 def nfvi_metrics(self, account, vim_id):
133 metrics = RwmonYang.NfviMetrics()
134 metrics.vcpu.utilization = 0.5
135 return metrics
136
137 def setUp(self):
138 self.loop = asyncio.new_event_loop()
139 self.logger = logging.getLogger('test-logger')
140
141 self.account = RwcalYang.CloudAccount(
142 name='test-cloud-account',
143 account_type="mock",
144 )
145
146 self.plugin_manager = NfviMetricsPluginManager(self.logger)
147 self.plugin_manager.register(self.account, "mock")
148
149 mock = self.plugin_manager.plugin(self.account.name)
150 mock.set_impl(TestNfviMetricsCache.Plugin())
151
152 self.vdur = VnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr_Vdur()
153 self.vdur.id = "test-vdur-id"
154 self.vdur.vim_id = "test-vim-id"
155 self.vdur.vm_flavor.vcpu_count = 4
156 self.vdur.vm_flavor.memory_mb = 1
157 self.vdur.vm_flavor.storage_gb = 1
158
159 def test_create_destroy_entry(self):
160 cache = NfviMetricsCache(self.logger, self.loop, self.plugin_manager)
161 self.assertEqual(len(cache._nfvi_metrics), 0)
162
163 cache.create_entry(self.account, self.vdur)
164 self.assertEqual(len(cache._nfvi_metrics), 1)
165
166 cache.destroy_entry(self.vdur.id)
167 self.assertEqual(len(cache._nfvi_metrics), 0)
168
169 def test_retrieve(self):
170 NfviMetrics.SAMPLE_INTERVAL = 1
171
172 cache = NfviMetricsCache(self.logger, self.loop, self.plugin_manager)
173 cache.create_entry(self.account, self.vdur)
174
175 @wait_for_pending_tasks(self.loop)
176 @asyncio.coroutine
177 def retrieve_metrics():
178 metrics = cache.retrieve("test-vim-id")
179 self.assertEqual(metrics.vcpu.utilization, 0.0)
180
181 yield from asyncio.sleep(NfviMetrics.SAMPLE_INTERVAL, loop=self.loop)
182
183 metrics = cache.retrieve("test-vim-id")
184 self.assertEqual(metrics.vcpu.utilization, 0.5)
185
186 self.loop.run_until_complete(retrieve_metrics())
187
188 def test_id_mapping(self):
189 cache = NfviMetricsCache(self.logger, self.loop, self.plugin_manager)
190
191 cache.create_entry(self.account, self.vdur)
192
193 self.assertEqual(cache.to_vim_id(self.vdur.id), self.vdur.vim_id)
194 self.assertEqual(cache.to_vdur_id(self.vdur.vim_id), self.vdur.id)
195 self.assertTrue(cache.contains_vdur_id(self.vdur.id))
196 self.assertTrue(cache.contains_vim_id(self.vdur.vim_id))
197
198 cache.destroy_entry(self.vdur.id)
199
200 self.assertFalse(cache.contains_vdur_id(self.vdur.id))
201 self.assertFalse(cache.contains_vim_id(self.vdur.vim_id))
202
203
204 class TestNfviMetrics(unittest.TestCase):
205 class Plugin(object):
206 def nfvi_metrics_available(self, cloud_account):
207 return True
208
209 def nfvi_metrics(self, account, vim_id):
210 metrics = RwVnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr_Vdur_NfviMetrics()
211 metrics.vcpu.utilization = 0.5
212 return None, metrics
213
214 def setUp(self):
215 self.loop = asyncio.new_event_loop()
216 self.account = RwcalYang.CloudAccount(
217 name='test-cloud-account',
218 account_type="mock",
219 )
220
221 self.plugin = TestNfviMetrics.Plugin()
222 self.logger = logging.getLogger('test-logger')
223
224 self.vdur = make_vdur()
225 self.vdur.vm_flavor.vcpu_count = 4
226 self.vdur.vm_flavor.memory_mb = 100
227 self.vdur.vm_flavor.storage_gb = 2
228 self.vdur.vim_id = 'test-vim-id'
229
230 def test_update(self):
231 nfvi_metrics = NfviMetrics(
232 self.logger,
233 self.loop,
234 self.account,
235 self.plugin,
236 self.vdur,
237 )
238
239 # Reduce the SAMPLE_INTERVAL so that the test does not take a long time
240 nfvi_metrics.SAMPLE_INTERVAL = 1
241
242 # The metrics have never been retrieved so they should be updated
243 self.assertTrue(nfvi_metrics.should_update())
244
245 # The metrics return will be empty because the cache version is empty.
246 # However, this trigger an update to retrieve metrics from the plugin.
247 metrics = nfvi_metrics.retrieve()
248 self.assertEqual(metrics.vcpu.utilization, 0.0)
249
250 # An update has been trigger by the retrieve call so additional updates
251 # should not happen
252 self.assertFalse(nfvi_metrics.should_update())
253 self.assertFalse(nfvi_metrics._updating.done())
254
255 # Allow the event loop to run until the update is complete
256 @asyncio.coroutine
257 @wait_for_pending_tasks(self.loop)
258 def wait_for_update():
259 yield from asyncio.wait_for(
260 nfvi_metrics._updating,
261 timeout=2,
262 loop=self.loop,
263 )
264
265 self.loop.run_until_complete(wait_for_update())
266
267 # Check that we have a new metrics object
268 metrics = nfvi_metrics.retrieve()
269 self.assertEqual(metrics.vcpu.utilization, 0.5)
270
271 # We have just updated the metrics so it should be unnecessary to update
272 # right now
273 self.assertFalse(nfvi_metrics.should_update())
274 self.assertTrue(nfvi_metrics._updating.done())
275
276 # Wait an amount of time equal to the SAMPLE_INTERVAL. This ensures
277 # that the metrics that were just retrieved become stale...
278 time.sleep(NfviMetrics.SAMPLE_INTERVAL)
279
280 # ...now it is time to update again
281 self.assertTrue(nfvi_metrics.should_update())
282
283
284 class TestNfviInterface(unittest.TestCase):
285 class NfviPluginImpl(object):
286 def __init__(self):
287 self._alarms = set()
288
289 def nfvi_metrics(self, account, vm_id):
290 return rwmon.NfviMetrics()
291
292 def nfvi_metrics_available(self, account):
293 return True
294
295 def alarm_create(self, account, vim_id, alarm):
296 alarm.alarm_id = str(uuid.uuid4())
297 self._alarms.add(alarm.alarm_id)
298 return RwTypes.RwStatus.SUCCESS
299
300 def alarm_delete(self, account, alarm_id):
301 self._alarms.remove(alarm_id)
302 return RwTypes.RwStatus.SUCCESS
303
304 def setUp(self):
305 self.loop = asyncio.new_event_loop()
306 self.logger = logging.getLogger('test-logger')
307
308 self.account = RwcalYang.CloudAccount(
309 name='test-cloud-account',
310 account_type="mock",
311 )
312
313 # Define the VDUR to avoid division by zero
314 self.vdur = make_vdur()
315 self.vdur.vm_flavor.vcpu_count = 4
316 self.vdur.vm_flavor.memory_mb = 100
317 self.vdur.vm_flavor.storage_gb = 2
318 self.vdur.vim_id = 'test-vim-id'
319
320 self.plugin_manager = NfviMetricsPluginManager(self.logger)
321 self.plugin_manager.register(self.account, "mock")
322
323 self.cache = NfviMetricsCache(
324 self.logger,
325 self.loop,
326 self.plugin_manager,
327 )
328
329 self.nfvi_interface = NfviInterface(
330 self.loop,
331 self.logger,
332 self.plugin_manager,
333 self.cache
334 )
335
336 def test_nfvi_metrics_available(self):
337 self.assertTrue(self.nfvi_interface.nfvi_metrics_available(self.account))
338
339 def test_retrieve(self):
340 pass
341
342 def test_alarm_create_and_destroy(self):
343 alarm = VnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr_Vdur_Alarms()
344 alarm.name = "test-alarm"
345 alarm.description = "test-description"
346 alarm.vdur_id = "test-vdur-id"
347 alarm.metric = "CPU_UTILIZATION"
348 alarm.statistic = "MINIMUM"
349 alarm.operation = "GT"
350 alarm.value = 0.1
351 alarm.period = 10
352 alarm.evaluations = 1
353
354 plugin_impl = TestNfviInterface.NfviPluginImpl()
355 plugin = self.plugin_manager.plugin(self.account.name)
356 plugin.set_impl(plugin_impl)
357
358 self.assertEqual(len(plugin_impl._alarms), 0)
359
360 @asyncio.coroutine
361 @wait_for_pending_tasks(self.loop)
362 def wait_for_create():
363 coro = self.nfvi_interface.alarm_create(
364 self.account,
365 "test-vim-id",
366 alarm,
367 )
368 yield from asyncio.wait_for(
369 coro,
370 timeout=2,
371 loop=self.loop,
372 )
373
374 self.loop.run_until_complete(wait_for_create())
375 self.assertEqual(len(plugin_impl._alarms), 1)
376 self.assertTrue(alarm.alarm_id is not None)
377
378 @asyncio.coroutine
379 @wait_for_pending_tasks(self.loop)
380 def wait_for_destroy():
381 coro = self.nfvi_interface.alarm_destroy(
382 self.account,
383 alarm.alarm_id,
384 )
385 yield from asyncio.wait_for(
386 coro,
387 timeout=2,
388 loop=self.loop,
389 )
390
391 self.loop.run_until_complete(wait_for_destroy())
392 self.assertEqual(len(plugin_impl._alarms), 0)
393
394
395 class TestVdurNfviMetrics(unittest.TestCase):
396 def setUp(self):
397 # Reduce the sample interval so that test run quickly
398 NfviMetrics.SAMPLE_INTERVAL = 0.1
399
400 # Create a mock plugin to define the metrics retrieved. The plugin will
401 # return a VCPU utilization of 0.5.
402 class MockPlugin(object):
403 def __init__(self):
404 self.metrics = RwmonYang.NfviMetrics()
405
406 def nfvi_metrics(self, account, vim_id):
407 self.metrics.vcpu.utilization = 0.5
408 return self.metrics
409
410 self.loop = asyncio.get_event_loop()
411 self.logger = logging.getLogger('test-logger')
412
413 self.account = RwcalYang.CloudAccount(
414 name='test-cloud-account',
415 account_type="mock",
416 )
417
418 # Define the VDUR to avoid division by zero
419 vdur = make_vdur()
420 vdur.vm_flavor.vcpu_count = 4
421 vdur.vm_flavor.memory_mb = 100
422 vdur.vm_flavor.storage_gb = 2
423 vdur.vim_id = 'test-vim-id'
424
425 # Instantiate the mock plugin
426 self.plugin_manager = NfviMetricsPluginManager(self.logger)
427 self.plugin_manager.register(self.account, "mock")
428
429 self.plugin = self.plugin_manager.plugin(self.account.name)
430 self.plugin.set_impl(MockPlugin())
431
432 self.cache = NfviMetricsCache(
433 self.logger,
434 self.loop,
435 self.plugin_manager,
436 )
437
438 self.manager = NfviInterface(
439 self.loop,
440 self.logger,
441 self.plugin_manager,
442 self.cache,
443 )
444
445 self.metrics = NfviMetrics(
446 self.logger,
447 self.loop,
448 self.account,
449 self.plugin,
450 vdur,
451 )
452
453 def test_retrieval(self):
454 metrics_a = None
455 metrics_b = None
456
457 # Define a coroutine that can be added to the asyncio event loop
458 @asyncio.coroutine
459 def update():
460 # Output from the metrics calls with be written to these nonlocal
461 # variables
462 nonlocal metrics_a
463 nonlocal metrics_b
464
465 # This first call will return the current metrics values and
466 # schedule a request to the NFVI to retrieve metrics from the data
467 # source. All metrics will be zero at this point.
468 metrics_a = self.metrics.retrieve()
469
470 # Wait for the scheduled update to take effect
471 yield from asyncio.sleep(0.2, loop=self.loop)
472
473 # Retrieve the updated metrics
474 metrics_b = self.metrics.retrieve()
475
476 self.loop.run_until_complete(update())
477
478 # Check that the metrics returned indicate that the plugin was queried
479 # and returned the appropriate value, i.e. 0.5 utilization
480 self.assertEqual(0.0, metrics_a.vcpu.utilization)
481 self.assertEqual(0.5, metrics_b.vcpu.utilization)
482
483
484 class TestNfviMetricsPluginManager(unittest.TestCase):
485 def setUp(self):
486 self.logger = logging.getLogger('test-logger')
487 self.plugins = NfviMetricsPluginManager(self.logger)
488 self.account = RwcalYang.CloudAccount(
489 name='test-cloud-account',
490 account_type="mock",
491 )
492
493 def test_mock_plugin(self):
494 # Register an account name with a mock plugin. If successful, the
495 # plugin manager should return a non-None object.
496 self.plugins.register(self.account, 'mock')
497 self.assertIsNotNone(self.plugins.plugin(self.account.name))
498
499 # Now unregister the cloud account
500 self.plugins.unregister(self.account.name)
501
502 # Trying to retrieve a plugin for a cloud account that has not been
503 # registered with the manager is expected to raise an exception.
504 with self.assertRaises(KeyError):
505 self.plugins.plugin(self.account.name)
506
507 def test_multiple_registration(self):
508 self.plugins.register(self.account, 'mock')
509
510 # Attempting to register the account with another type of plugin will
511 # also cause an exception to be raised.
512 with self.assertRaises(AccountAlreadyRegisteredError):
513 self.plugins.register(self.account, 'mock')
514
515 # Attempting to register the account with 'openstack' again with cause
516 # an exception to be raised.
517 with self.assertRaises(AccountAlreadyRegisteredError):
518 self.plugins.register(self.account, 'openstack')
519
520 def test_unsupported_plugin(self):
521 # If an attempt is made to register a cloud account with an unknown
522 # type of plugin, a PluginNotSupportedError should be raised.
523 with self.assertRaises(PluginNotSupportedError):
524 self.plugins.register(self.account, 'unsupported-plugin')
525
526 def test_anavailable_plugin(self):
527 # Create a factory that always raises PluginUnavailableError
528 class UnavailablePluginFactory(PluginFactory):
529 PLUGIN_NAME = "unavailable-plugin"
530
531 def create(self, cloud_account):
532 raise PluginUnavailableError()
533
534 # Register the factory
535 self.plugins.register_plugin_factory(UnavailablePluginFactory())
536
537 # Ensure that the correct exception propagates when the cloud account
538 # is registered.
539 with self.assertRaises(PluginUnavailableError):
540 self.plugins.register(self.account, "unavailable-plugin")
541
542
543 class TestMonitor(unittest.TestCase):
544 """
545 The Monitor class is the implementation that is called by the
546 MonitorTasklet. It provides the unified interface for controlling and
547 querying the monitoring functionality.
548 """
549
550 def setUp(self):
551 # Reduce the sample interval so that test run quickly
552 NfviMetrics.SAMPLE_INTERVAL = 0.1
553
554 self.loop = asyncio.get_event_loop()
555 self.logger = logging.getLogger('test-logger')
556 self.config = InstanceConfiguration()
557 self.monitor = Monitor(self.loop, self.logger, self.config)
558
559 self.account = RwcalYang.CloudAccount(
560 name='test-cloud-account',
561 account_type="mock",
562 )
563
564 def test_instance_config(self):
565 """
566 Configuration data for an instance is pass to the Monitor when it is
567 created. The data is passed in the InstanceConfiguration object. This
568 object is typically shared between the tasklet and the monitor, and
569 provides a way for the tasklet to update the configuration of the
570 monitor.
571 """
572 self.assertTrue(hasattr(self.monitor._config, "polling_period"))
573 self.assertTrue(hasattr(self.monitor._config, "min_cache_lifetime"))
574 self.assertTrue(hasattr(self.monitor._config, "max_polling_frequency"))
575
576 def test_monitor_cloud_accounts(self):
577 """
578 This test checks the cloud accounts are correctly added and deleted,
579 and that the correct exceptions are raised on duplicate adds or
580 deletes.
581
582 """
583 # Add the cloud account to the monitor
584 self.monitor.add_cloud_account(self.account)
585 self.assertIn(self.account.name, self.monitor._cloud_accounts)
586
587 # Add the cloud account to the monitor again
588 with self.assertRaises(AccountAlreadyRegisteredError):
589 self.monitor.add_cloud_account(self.account)
590
591 # Delete the cloud account
592 self.monitor.remove_cloud_account(self.account.name)
593 self.assertNotIn(self.account.name, self.monitor._cloud_accounts)
594
595 # Delete the cloud account again
596 with self.assertRaises(UnknownAccountError):
597 self.monitor.remove_cloud_account(self.account.name)
598
599 def test_monitor_cloud_accounts_illegal_removal(self):
600 """
601 A cloud account may not be removed while there are plugins or records
602 that are associated with it. Attempting to delete such a cloud account
603 will raise an exception.
604 """
605 # Add the cloud account to the monitor
606 self.monitor.add_cloud_account(self.account)
607
608 # Create a VNFR associated with the cloud account
609 vnfr = RwVnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr()
610 vnfr.cloud_account = self.account.name
611 vnfr.id = 'test-vnfr-id'
612
613 # Add a VDUR to the VNFR
614 vdur = vnfr.vdur.add()
615 vdur.vim_id = 'test-vim-id-1'
616 vdur.id = 'test-vdur-id-1'
617
618 # Now add the VNFR to the monitor
619 self.monitor.add_vnfr(vnfr)
620
621 # Check that the monitor contains the VNFR, VDUR, and metrics
622 self.assertTrue(self.monitor.is_registered_vdur(vdur.id))
623 self.assertTrue(self.monitor.is_registered_vnfr(vnfr.id))
624 self.assertEqual(1, len(self.monitor.metrics))
625
626 # Deleting the cloud account now should raise an exception because the
627 # VNFR and VDUR are associated with the cloud account.
628 with self.assertRaises(AccountInUseError):
629 self.monitor.remove_cloud_account(self.account.name)
630
631 # Now remove the VNFR from the monitor
632 self.monitor.remove_vnfr(vnfr.id)
633 self.assertFalse(self.monitor.is_registered_vdur(vdur.id))
634 self.assertFalse(self.monitor.is_registered_vnfr(vnfr.id))
635 self.assertEqual(0, len(self.monitor.metrics))
636
637 # Safely delete the cloud account
638 self.monitor.remove_cloud_account(self.account.name)
639
640 def test_vdur_registration(self):
641 """
642 When a VDUR is registered with the Monitor it is registered with the
643 VdurNfviMetricsManager. Thus it is assigned a plugin that can be used
644 to retrieve the NFVI metrics associated with the VDU.
645 """
646 # Define the VDUR to be registered
647 vdur = VnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr_Vdur()
648 vdur.vm_flavor.vcpu_count = 4
649 vdur.vm_flavor.memory_mb = 100
650 vdur.vm_flavor.storage_gb = 2
651 vdur.vim_id = 'test-vim-id'
652 vdur.id = 'test-vdur-id'
653
654 # Before registering the VDUR, the cloud account needs to be added to
655 # the monitor.
656 self.monitor.add_cloud_account(self.account)
657
658 # Register the VDUR with the monitor
659 self.monitor.add_vdur(self.account, vdur)
660 self.assertTrue(self.monitor.is_registered_vdur(vdur.id))
661
662 # Check that the VDUR has been added to the metrics cache
663 self.assertTrue(self.monitor.cache.contains_vdur_id(vdur.id))
664
665 # Unregister the VDUR
666 self.monitor.remove_vdur(vdur.id)
667 self.assertFalse(self.monitor.is_registered_vdur(vdur.id))
668
669 # Check that the VDUR has been removed from the metrics cache
670 self.assertFalse(self.monitor.cache.contains_vdur_id(vdur.id))
671
672 def test_vnfr_add_update_delete(self):
673 """
674 When a VNFR is added to the Monitor a record is created of the
675 relationship between the VNFR and any VDURs that it contains. Each VDUR
676 is then registered with the VdurNfviMetricsManager. A VNFR can also be
677 updated so that it contains more of less VDURs. Any VDURs that are
678 added to the VNFR are registered with the NdurNfviMetricsManager, and
679 any that are removed are unregistered. When a VNFR is deleted, all of
680 the VDURs contained in the VNFR are unregistered.
681 """
682 # Define the VDUR to be registered
683 vdur = RwVnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr_Vdur()
684 vdur.vim_id = 'test-vim-id-1'
685 vdur.id = 'test-vdur-id-1'
686
687 vnfr = RwVnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr()
688 vnfr.cloud_account = self.account.name
689 vnfr.id = 'test-vnfr-id'
690
691 vnfr.vdur.append(vdur)
692
693 self.monitor.add_cloud_account(self.account)
694
695 # Add the VNFR to the monitor. This will also register VDURs contained
696 # in the VNFR with the monitor.
697 self.monitor.add_vnfr(vnfr)
698 self.assertTrue(self.monitor.is_registered_vdur('test-vdur-id-1'))
699
700 # Add another VDUR to the VNFR and update the monitor. Both VDURs
701 # should now be registered
702 vdur = RwVnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr_Vdur()
703 vdur.vim_id = 'test-vim-id-2'
704 vdur.id = 'test-vdur-id-2'
705
706 vnfr.vdur.append(vdur)
707
708 self.monitor.update_vnfr(vnfr)
709 self.assertTrue(self.monitor.is_registered_vdur('test-vdur-id-1'))
710 self.assertTrue(self.monitor.is_registered_vdur('test-vdur-id-2'))
711
712 # Delete the VNFR from the monitor. This should remove the VNFR and all
713 # of the associated VDURs from the monitor.
714 self.monitor.remove_vnfr(vnfr.id)
715 self.assertFalse(self.monitor.is_registered_vnfr('test-vnfr-id'))
716 self.assertFalse(self.monitor.is_registered_vdur('test-vdur-id-1'))
717 self.assertFalse(self.monitor.is_registered_vdur('test-vdur-id-2'))
718
719 with self.assertRaises(KeyError):
720 self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
721
722 with self.assertRaises(KeyError):
723 self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
724
725 def test_complete(self):
726 """
727 This test simulates the addition of a VNFR to the Monitor (along with
728 updates), and retrieves NFVI metrics from the VDUR. The VNFR is then
729 deleted, which should result in a cleanup of all the data in the
730 Monitor.
731 """
732 # Create the VNFR
733 vnfr = RwVnfrYang.YangData_Vnfr_VnfrCatalog_Vnfr()
734 vnfr.cloud_account = self.account.name
735 vnfr.id = 'test-vnfr-id'
736
737 # Create 2 VDURs
738 vdur = vnfr.vdur.add()
739 vdur.id = 'test-vdur-id-1'
740 vdur.vim_id = 'test-vim-id-1'
741 vdur.vm_flavor.vcpu_count = 4
742 vdur.vm_flavor.memory_mb = 100
743 vdur.vm_flavor.storage_gb = 2
744
745 vdur = vnfr.vdur.add()
746 vdur.id = 'test-vdur-id-2'
747 vdur.vim_id = 'test-vim-id-2'
748 vdur.vm_flavor.vcpu_count = 4
749 vdur.vm_flavor.memory_mb = 100
750 vdur.vm_flavor.storage_gb = 2
751
752 class MockPlugin(object):
753 def __init__(self):
754 self._metrics = dict()
755 self._metrics['test-vim-id-1'] = RwmonYang.NfviMetrics()
756 self._metrics['test-vim-id-2'] = RwmonYang.NfviMetrics()
757
758 def nfvi_metrics(self, account, vim_id):
759 metrics = self._metrics[vim_id]
760
761 if vim_id == 'test-vim-id-1':
762 metrics.memory.used += 1000
763 else:
764 metrics.memory.used += 2000
765
766 return metrics
767
768 class MockFactory(PluginFactory):
769 PLUGIN_NAME = "mock"
770
771 def create(self, cloud_account):
772 plugin = rw_peas.PeasPlugin("rwmon_mock", 'RwMon-1.0')
773 impl = plugin.get_interface("Monitoring")
774 impl.set_impl(MockPlugin())
775 return impl
776
777 # Modify the mock plugin factory
778 self.monitor._nfvi_plugins._factories["mock"] = MockFactory()
779
780 # Add the cloud account the monitor
781 self.monitor.add_cloud_account(self.account)
782
783 # Add the VNFR to the monitor.
784 self.monitor.add_vnfr(vnfr)
785
786 @wait_for_pending_tasks(self.loop)
787 @asyncio.coroutine
788 def call1():
789 # call #1 (time = 0.00s)
790 # The metrics for these VDURs have not been populated yet so a
791 # default metrics object (all zeros) is returned, and a request is
792 # scheduled with the data source to retrieve the metrics.
793 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
794 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
795
796 self.assertEqual(0, metrics1.memory.used)
797 self.assertEqual(0, metrics2.memory.used)
798
799 self.loop.run_until_complete(call1())
800
801 @wait_for_pending_tasks(self.loop)
802 @asyncio.coroutine
803 def call2():
804 # call #2 (wait 0.05s)
805 # The metrics have been populated with data from the data source
806 # due to the request made during call #1.
807 yield from asyncio.sleep(0.05)
808
809 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
810 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
811
812 self.assertEqual(1000, metrics1.memory.used)
813 self.assertEqual(2000, metrics2.memory.used)
814
815 self.loop.run_until_complete(call2())
816
817 @wait_for_pending_tasks(self.loop)
818 @asyncio.coroutine
819 def call3():
820 # call #3 (wait 0.50s)
821 # This call exceeds 0.1s (the sample interval of the plugin)
822 # from when the data was retrieved. The cached metrics are
823 # immediately returned, but a request is made to the data source to
824 # refresh these metrics.
825 yield from asyncio.sleep(0.10)
826
827 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
828 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
829
830 self.assertEqual(1000, metrics1.memory.used)
831 self.assertEqual(2000, metrics2.memory.used)
832
833 self.loop.run_until_complete(call3())
834
835 @wait_for_pending_tasks(self.loop)
836 @asyncio.coroutine
837 def call4():
838 # call #4 (wait 1.00s)
839 # The metrics retrieved differ from those in call #3 because the
840 # cached metrics have been updated.
841 yield from asyncio.sleep(0.10)
842 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
843 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
844
845 self.assertEqual(2000, metrics1.memory.used)
846 self.assertEqual(4000, metrics2.memory.used)
847
848 self.loop.run_until_complete(call4())
849
850
851 def main(argv=sys.argv[1:]):
852 logging.basicConfig(format='TEST %(message)s')
853
854 parser = argparse.ArgumentParser()
855 parser.add_argument('-v', '--verbose', action='store_true')
856
857 args = parser.parse_args(argv)
858
859 # Set the global logging level
860 logging.getLogger().setLevel(logging.DEBUG if args.verbose else logging.ERROR)
861
862 # Set the logger in this test to use a null handler
863 logging.getLogger('test-logger').addHandler(logging.NullHandler())
864
865 # The unittest framework requires a program name, so use the name of this
866 # file instead (we do not want to have to pass a fake program name to main
867 # when this is called from the interpreter).
868 unittest.main(argv=[__file__] + argv,
869 testRunner=xmlrunner.XMLTestRunner(
870 output=os.environ["RIFT_MODULE_TEST"]))
871
872 if __name__ == '__main__':
873 main()