update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b second try
[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.YangData_RwProject_Project_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.YangData_RwProject_Project_CloudAccounts_CloudAccountList(
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.YangData_RwProject_Project_CloudAccounts_CloudAccountList(
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.YangData_RwProject_Project_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.YangData_RwProject_Project_CloudAccounts_CloudAccountList(
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 @unittest.skip("Alarms are being disabled in monitor")
344 def test_alarm_create_and_destroy(self):
345 alarm = VnfrYang.YangData_RwProject_Project_VnfrCatalog_Vnfr_Vdur_Alarms()
346 alarm.name = "test-alarm"
347 alarm.description = "test-description"
348 alarm.vdur_id = "test-vdur-id"
349 alarm.metric = "CPU_UTILIZATION"
350 alarm.statistic = "MINIMUM"
351 alarm.operation = "GT"
352 alarm.value = 0.1
353 alarm.period = 10
354 alarm.evaluations = 1
355
356 plugin_impl = TestNfviInterface.NfviPluginImpl()
357 plugin = self.plugin_manager.plugin(self.account.name)
358 plugin.set_impl(plugin_impl)
359
360 self.assertEqual(len(plugin_impl._alarms), 0)
361
362 @asyncio.coroutine
363 @wait_for_pending_tasks(self.loop)
364 def wait_for_create():
365 coro = self.nfvi_interface.alarm_create(
366 self.account,
367 "test-vim-id",
368 alarm,
369 )
370 yield from asyncio.wait_for(
371 coro,
372 timeout=2,
373 loop=self.loop,
374 )
375
376 self.loop.run_until_complete(wait_for_create())
377 self.assertEqual(len(plugin_impl._alarms), 1)
378 self.assertTrue(alarm.alarm_id is not None)
379
380 @asyncio.coroutine
381 @wait_for_pending_tasks(self.loop)
382 def wait_for_destroy():
383 coro = self.nfvi_interface.alarm_destroy(
384 self.account,
385 alarm.alarm_id,
386 )
387 yield from asyncio.wait_for(
388 coro,
389 timeout=2,
390 loop=self.loop,
391 )
392
393 self.loop.run_until_complete(wait_for_destroy())
394 self.assertEqual(len(plugin_impl._alarms), 0)
395
396
397 class TestVdurNfviMetrics(unittest.TestCase):
398 def setUp(self):
399 # Reduce the sample interval so that test run quickly
400 NfviMetrics.SAMPLE_INTERVAL = 0.1
401
402 # Create a mock plugin to define the metrics retrieved. The plugin will
403 # return a VCPU utilization of 0.5.
404 class MockPlugin(object):
405 def __init__(self):
406 self.metrics = RwmonYang.YangData_RwProject_Project_NfviMetrics()
407
408 def nfvi_metrics(self, account, vim_id):
409 self.metrics.vcpu.utilization = 0.5
410 return self.metrics
411
412 self.loop = asyncio.get_event_loop()
413 self.logger = logging.getLogger('test-logger')
414
415 self.account = RwcalYang.YangData_RwProject_Project_CloudAccounts_CloudAccountList(
416 name='test-cloud-account',
417 account_type="mock",
418 )
419
420 # Define the VDUR to avoid division by zero
421 vdur = make_vdur()
422 vdur.vm_flavor.vcpu_count = 4
423 vdur.vm_flavor.memory_mb = 100
424 vdur.vm_flavor.storage_gb = 2
425 vdur.vim_id = 'test-vim-id'
426
427 # Instantiate the mock plugin
428 self.plugin_manager = NfviMetricsPluginManager(self.logger)
429 self.plugin_manager.register(self.account, "mock")
430
431 self.plugin = self.plugin_manager.plugin(self.account.name)
432 self.plugin.set_impl(MockPlugin())
433
434 self.cache = NfviMetricsCache(
435 self.logger,
436 self.loop,
437 self.plugin_manager,
438 )
439
440 self.manager = NfviInterface(
441 self.loop,
442 self.logger,
443 self.plugin_manager,
444 self.cache,
445 )
446
447 self.metrics = NfviMetrics(
448 self.logger,
449 self.loop,
450 self.account,
451 self.plugin,
452 vdur,
453 )
454
455 def test_retrieval(self):
456 metrics_a = None
457 metrics_b = None
458
459 # Define a coroutine that can be added to the asyncio event loop
460 @asyncio.coroutine
461 def update():
462 # Output from the metrics calls with be written to these nonlocal
463 # variables
464 nonlocal metrics_a
465 nonlocal metrics_b
466
467 # This first call will return the current metrics values and
468 # schedule a request to the NFVI to retrieve metrics from the data
469 # source. All metrics will be zero at this point.
470 metrics_a = self.metrics.retrieve()
471
472 # Wait for the scheduled update to take effect
473 yield from asyncio.sleep(0.2, loop=self.loop)
474
475 # Retrieve the updated metrics
476 metrics_b = self.metrics.retrieve()
477
478 self.loop.run_until_complete(update())
479
480 # Check that the metrics returned indicate that the plugin was queried
481 # and returned the appropriate value, i.e. 0.5 utilization
482 self.assertEqual(0.0, metrics_a.vcpu.utilization)
483 self.assertEqual(0.5, metrics_b.vcpu.utilization)
484
485
486 class TestNfviMetricsPluginManager(unittest.TestCase):
487 def setUp(self):
488 self.logger = logging.getLogger('test-logger')
489 self.plugins = NfviMetricsPluginManager(self.logger)
490 self.account = RwcalYang.YangData_RwProject_Project_CloudAccounts_CloudAccountList(
491 name='test-cloud-account',
492 account_type="mock",
493 )
494
495 def test_mock_plugin(self):
496 # Register an account name with a mock plugin. If successful, the
497 # plugin manager should return a non-None object.
498 self.plugins.register(self.account, 'mock')
499 self.assertIsNotNone(self.plugins.plugin(self.account.name))
500
501 # Now unregister the cloud account
502 self.plugins.unregister(self.account.name)
503
504 # Trying to retrieve a plugin for a cloud account that has not been
505 # registered with the manager is expected to raise an exception.
506 with self.assertRaises(KeyError):
507 self.plugins.plugin(self.account.name)
508
509 def test_multiple_registration(self):
510 self.plugins.register(self.account, 'mock')
511
512 # Attempting to register the account with another type of plugin will
513 # also cause an exception to be raised.
514 with self.assertRaises(AccountAlreadyRegisteredError):
515 self.plugins.register(self.account, 'mock')
516
517 # Attempting to register the account with 'openstack' again with cause
518 # an exception to be raised.
519 with self.assertRaises(AccountAlreadyRegisteredError):
520 self.plugins.register(self.account, 'openstack')
521
522 def test_unsupported_plugin(self):
523 # If an attempt is made to register a cloud account with an unknown
524 # type of plugin, a PluginNotSupportedError should be raised.
525 with self.assertRaises(PluginNotSupportedError):
526 self.plugins.register(self.account, 'unsupported-plugin')
527
528 def test_anavailable_plugin(self):
529 # Create a factory that always raises PluginUnavailableError
530 class UnavailablePluginFactory(PluginFactory):
531 PLUGIN_NAME = "unavailable-plugin"
532
533 def create(self, cloud_account):
534 raise PluginUnavailableError()
535
536 # Register the factory
537 self.plugins.register_plugin_factory(UnavailablePluginFactory())
538
539 # Ensure that the correct exception propagates when the cloud account
540 # is registered.
541 with self.assertRaises(PluginUnavailableError):
542 self.plugins.register(self.account, "unavailable-plugin")
543
544
545 class TestMonitor(unittest.TestCase):
546 """
547 The Monitor class is the implementation that is called by the
548 MonitorTasklet. It provides the unified interface for controlling and
549 querying the monitoring functionality.
550 """
551
552 def setUp(self):
553 # Reduce the sample interval so that test run quickly
554 NfviMetrics.SAMPLE_INTERVAL = 0.1
555
556 self.loop = asyncio.get_event_loop()
557 self.logger = logging.getLogger('test-logger')
558 self.project = ManoProject(self.logger, name=DEFAULT_PROJECT)
559 self.config = InstanceConfiguration()
560 self.monitor = Monitor(self.loop, self.logger, self.config, self.project)
561
562 self.account = RwcalYang.YangData_RwProject_Project_CloudAccounts_CloudAccountList(
563 name='test-cloud-account',
564 account_type="mock",
565 )
566
567 def test_instance_config(self):
568 """
569 Configuration data for an instance is pass to the Monitor when it is
570 created. The data is passed in the InstanceConfiguration object. This
571 object is typically shared between the tasklet and the monitor, and
572 provides a way for the tasklet to update the configuration of the
573 monitor.
574 """
575 self.assertTrue(hasattr(self.monitor._config, "polling_period"))
576 self.assertTrue(hasattr(self.monitor._config, "min_cache_lifetime"))
577 self.assertTrue(hasattr(self.monitor._config, "max_polling_frequency"))
578
579 def test_monitor_cloud_accounts(self):
580 """
581 This test checks the cloud accounts are correctly added and deleted,
582 and that the correct exceptions are raised on duplicate adds or
583 deletes.
584
585 """
586 # Add the cloud account to the monitor
587 self.monitor.add_cloud_account(self.account)
588 self.assertIn(self.account.name, self.monitor._cloud_accounts)
589
590 # Add the cloud account to the monitor again
591 with self.assertRaises(AccountAlreadyRegisteredError):
592 self.monitor.add_cloud_account(self.account)
593
594 # Delete the cloud account
595 self.monitor.remove_cloud_account(self.account.name)
596 self.assertNotIn(self.account.name, self.monitor._cloud_accounts)
597
598 # Delete the cloud account again
599 with self.assertRaises(UnknownAccountError):
600 self.monitor.remove_cloud_account(self.account.name)
601
602 def test_monitor_cloud_accounts_illegal_removal(self):
603 """
604 A cloud account may not be removed while there are plugins or records
605 that are associated with it. Attempting to delete such a cloud account
606 will raise an exception.
607 """
608 # Add the cloud account to the monitor
609 self.monitor.add_cloud_account(self.account)
610
611 # Create a VNFR associated with the cloud account
612 vnfr = RwVnfrYang.YangData_RwProject_Project_VnfrCatalog_Vnfr()
613 vnfr.datacenter = self.account.name
614 vnfr.id = 'test-vnfr-id'
615
616 # Add a VDUR to the VNFR
617 vdur = vnfr.vdur.add()
618 vdur.vim_id = 'test-vim-id-1'
619 vdur.id = 'test-vdur-id-1'
620
621 # Now add the VNFR to the monitor
622 self.monitor.add_vnfr(vnfr)
623
624 # Check that the monitor contains the VNFR, VDUR, and metrics
625 self.assertTrue(self.monitor.is_registered_vdur(vdur.id))
626 self.assertTrue(self.monitor.is_registered_vnfr(vnfr.id))
627 self.assertEqual(1, len(self.monitor.metrics))
628
629 # Deleting the cloud account now should raise an exception because the
630 # VNFR and VDUR are associated with the cloud account.
631 with self.assertRaises(AccountInUseError):
632 self.monitor.remove_cloud_account(self.account.name)
633
634 # Now remove the VNFR from the monitor
635 self.monitor.remove_vnfr(vnfr.id)
636 self.assertFalse(self.monitor.is_registered_vdur(vdur.id))
637 self.assertFalse(self.monitor.is_registered_vnfr(vnfr.id))
638 self.assertEqual(0, len(self.monitor.metrics))
639
640 # Safely delete the cloud account
641 self.monitor.remove_cloud_account(self.account.name)
642
643 def test_vdur_registration(self):
644 """
645 When a VDUR is registered with the Monitor it is registered with the
646 VdurNfviMetricsManager. Thus it is assigned a plugin that can be used
647 to retrieve the NFVI metrics associated with the VDU.
648 """
649 # Define the VDUR to be registered
650 vdur = VnfrYang.YangData_RwProject_Project_VnfrCatalog_Vnfr_Vdur()
651 vdur.vm_flavor.vcpu_count = 4
652 vdur.vm_flavor.memory_mb = 100
653 vdur.vm_flavor.storage_gb = 2
654 vdur.vim_id = 'test-vim-id'
655 vdur.id = 'test-vdur-id'
656
657 # Before registering the VDUR, the cloud account needs to be added to
658 # the monitor.
659 self.monitor.add_cloud_account(self.account)
660
661 # Register the VDUR with the monitor
662 self.monitor.add_vdur(self.account, vdur)
663 self.assertTrue(self.monitor.is_registered_vdur(vdur.id))
664
665 # Check that the VDUR has been added to the metrics cache
666 self.assertTrue(self.monitor.cache.contains_vdur_id(vdur.id))
667
668 # Unregister the VDUR
669 self.monitor.remove_vdur(vdur.id)
670 self.assertFalse(self.monitor.is_registered_vdur(vdur.id))
671
672 # Check that the VDUR has been removed from the metrics cache
673 self.assertFalse(self.monitor.cache.contains_vdur_id(vdur.id))
674
675 def test_vnfr_add_update_delete(self):
676 """
677 When a VNFR is added to the Monitor a record is created of the
678 relationship between the VNFR and any VDURs that it contains. Each VDUR
679 is then registered with the VdurNfviMetricsManager. A VNFR can also be
680 updated so that it contains more of less VDURs. Any VDURs that are
681 added to the VNFR are registered with the NdurNfviMetricsManager, and
682 any that are removed are unregistered. When a VNFR is deleted, all of
683 the VDURs contained in the VNFR are unregistered.
684 """
685 # Define the VDUR to be registered
686 vdur = RwVnfrYang.YangData_RwProject_Project_VnfrCatalog_Vnfr_Vdur()
687 vdur.vim_id = 'test-vim-id-1'
688 vdur.id = 'test-vdur-id-1'
689
690 vnfr = RwVnfrYang.YangData_RwProject_Project_VnfrCatalog_Vnfr()
691 vnfr.datacenter = self.account.name
692 vnfr.id = 'test-vnfr-id'
693
694 vnfr.vdur.append(vdur)
695
696 self.monitor.add_cloud_account(self.account)
697
698 # Add the VNFR to the monitor. This will also register VDURs contained
699 # in the VNFR with the monitor.
700 self.monitor.add_vnfr(vnfr)
701 self.assertTrue(self.monitor.is_registered_vdur('test-vdur-id-1'))
702
703 # Add another VDUR to the VNFR and update the monitor. Both VDURs
704 # should now be registered
705 vdur = RwVnfrYang.YangData_RwProject_Project_VnfrCatalog_Vnfr_Vdur()
706 vdur.vim_id = 'test-vim-id-2'
707 vdur.id = 'test-vdur-id-2'
708
709 vnfr.vdur.append(vdur)
710
711 self.monitor.update_vnfr(vnfr)
712 self.assertTrue(self.monitor.is_registered_vdur('test-vdur-id-1'))
713 self.assertTrue(self.monitor.is_registered_vdur('test-vdur-id-2'))
714
715 # Delete the VNFR from the monitor. This should remove the VNFR and all
716 # of the associated VDURs from the monitor.
717 self.monitor.remove_vnfr(vnfr.id)
718 self.assertFalse(self.monitor.is_registered_vnfr('test-vnfr-id'))
719 self.assertFalse(self.monitor.is_registered_vdur('test-vdur-id-1'))
720 self.assertFalse(self.monitor.is_registered_vdur('test-vdur-id-2'))
721
722 with self.assertRaises(KeyError):
723 self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
724
725 with self.assertRaises(KeyError):
726 self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
727
728 def test_complete(self):
729 """
730 This test simulates the addition of a VNFR to the Monitor (along with
731 updates), and retrieves NFVI metrics from the VDUR. The VNFR is then
732 deleted, which should result in a cleanup of all the data in the
733 Monitor.
734 """
735 # Create the VNFR
736 vnfr = RwVnfrYang.YangData_RwProject_Project_VnfrCatalog_Vnfr()
737 vnfr.datacenter = self.account.name
738 vnfr.id = 'test-vnfr-id'
739
740 # Create 2 VDURs
741 vdur = vnfr.vdur.add()
742 vdur.id = 'test-vdur-id-1'
743 vdur.vim_id = 'test-vim-id-1'
744 vdur.vm_flavor.vcpu_count = 4
745 vdur.vm_flavor.memory_mb = 100
746 vdur.vm_flavor.storage_gb = 2
747
748 vdur = vnfr.vdur.add()
749 vdur.id = 'test-vdur-id-2'
750 vdur.vim_id = 'test-vim-id-2'
751 vdur.vm_flavor.vcpu_count = 4
752 vdur.vm_flavor.memory_mb = 100
753 vdur.vm_flavor.storage_gb = 2
754
755 class MockPlugin(object):
756 def __init__(self):
757 self._metrics = dict()
758 self._metrics['test-vim-id-1'] = RwmonYang.YangData_RwProject_Project_NfviMetrics()
759 self._metrics['test-vim-id-2'] = RwmonYang.YangData_RwProject_Project_NfviMetrics()
760
761 def nfvi_metrics(self, account, vim_id):
762 metrics = self._metrics[vim_id]
763
764 if vim_id == 'test-vim-id-1':
765 metrics.memory.used += 1000
766 else:
767 metrics.memory.used += 2000
768
769 return metrics
770
771 class MockFactory(PluginFactory):
772 PLUGIN_NAME = "mock"
773
774 def create(self, cloud_account):
775 plugin = rw_peas.PeasPlugin("rwmon_mock", 'RwMon-1.0')
776 impl = plugin.get_interface("Monitoring")
777 impl.set_impl(MockPlugin())
778 return impl
779
780 # Modify the mock plugin factory
781 self.monitor._nfvi_plugins._factories["mock"] = MockFactory()
782
783 # Add the cloud account the monitor
784 self.monitor.add_cloud_account(self.account)
785
786 # Add the VNFR to the monitor.
787 self.monitor.add_vnfr(vnfr)
788
789 @wait_for_pending_tasks(self.loop)
790 @asyncio.coroutine
791 def call1():
792 # call #1 (time = 0.00s)
793 # The metrics for these VDURs have not been populated yet so a
794 # default metrics object (all zeros) is returned, and a request is
795 # scheduled with the data source to retrieve the metrics.
796 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
797 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
798
799 self.assertEqual(0, metrics1.memory.used)
800 self.assertEqual(0, metrics2.memory.used)
801
802 self.loop.run_until_complete(call1())
803
804 @wait_for_pending_tasks(self.loop)
805 @asyncio.coroutine
806 def call2():
807 # call #2 (wait 0.05s)
808 # The metrics have been populated with data from the data source
809 # due to the request made during call #1.
810 yield from asyncio.sleep(0.05)
811
812 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
813 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
814
815 self.assertEqual(1000, metrics1.memory.used)
816 self.assertEqual(2000, metrics2.memory.used)
817
818 self.loop.run_until_complete(call2())
819
820 @wait_for_pending_tasks(self.loop)
821 @asyncio.coroutine
822 def call3():
823 # call #3 (wait 0.50s)
824 # This call exceeds 0.1s (the sample interval of the plugin)
825 # from when the data was retrieved. The cached metrics are
826 # immediately returned, but a request is made to the data source to
827 # refresh these metrics.
828 yield from asyncio.sleep(0.10)
829
830 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
831 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
832
833 self.assertEqual(1000, metrics1.memory.used)
834 self.assertEqual(2000, metrics2.memory.used)
835
836 self.loop.run_until_complete(call3())
837
838 @wait_for_pending_tasks(self.loop)
839 @asyncio.coroutine
840 def call4():
841 # call #4 (wait 1.00s)
842 # The metrics retrieved differ from those in call #3 because the
843 # cached metrics have been updated.
844 yield from asyncio.sleep(0.10)
845 metrics1 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-1')
846 metrics2 = self.monitor.retrieve_nfvi_metrics('test-vdur-id-2')
847
848 self.assertEqual(2000, metrics1.memory.used)
849 self.assertEqual(4000, metrics2.memory.used)
850
851 self.loop.run_until_complete(call4())
852
853
854 def main(argv=sys.argv[1:]):
855 logging.basicConfig(format='TEST %(message)s')
856
857 parser = argparse.ArgumentParser()
858 parser.add_argument('-v', '--verbose', action='store_true')
859
860 args = parser.parse_args(argv)
861
862 # Set the global logging level
863 logging.getLogger().setLevel(logging.DEBUG if args.verbose else logging.ERROR)
864
865 # Set the logger in this test to use a null handler
866 logging.getLogger('test-logger').addHandler(logging.NullHandler())
867
868 # The unittest framework requires a program name, so use the name of this
869 # file instead (we do not want to have to pass a fake program name to main
870 # when this is called from the interpreter).
871 unittest.main(argv=[__file__] + argv,
872 testRunner=xmlrunner.XMLTestRunner(
873 output=os.environ["RIFT_MODULE_TEST"]))
874
875 if __name__ == '__main__':
876 main()