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