Merge ~txiao/charm-storage-connector:iscsi-discovery-and-login into charm-storage-connector:defer-service-restarts-integration

Proposed by Tianqi Xiao
Status: Merged
Approved by: Tianqi Xiao
Approved revision: 37b83ef6eab6b315e36e14bc53961e4af918d089
Merged at revision: 37b83ef6eab6b315e36e14bc53961e4af918d089
Proposed branch: ~txiao/charm-storage-connector:iscsi-discovery-and-login
Merge into: charm-storage-connector:defer-service-restarts-integration
Diff against target: 252 lines (+85/-57)
4 files modified
config.yaml (+5/-4)
requirements.txt (+4/-2)
src/charm.py (+7/-15)
tests/unit/test_charm.py (+69/-36)
Reviewer Review Type Date Requested Status
Paul Goins Approve
🤖 prod-jenkaas-bootstack (community) continuous-integration Approve
Andrea Ieri Approve
Review via email: mp+438419@code.launchpad.net

Commit message

Add logic to allow auto discovery and login after iscsi service restart

To post a comment you must log in.
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Andrea Ieri (aieri) wrote (last edit ):

looks generally good, just a few small comments

and of course the CI failure needs to be investigated

review: Needs Fixing
Revision history for this message
Tianqi Xiao (txiao) wrote :

The CI failure on bionic is unrelated to this MP. I've ran func tests against the upstream repo and observed the same error: https://pastebin.canonical.com/p/6SXgjjkb8V/.

After some digging, I found out ops library is trying to import dataclasses, which only exist in python 3.7+ (bionic is on python 3.6 by default). And according to their release note, ops2 supports only python3.8+. This change was introduced 2 weeks ago in this commit [0], which means all charms built with latest ops library would not be able to run on bionic machine.

[0]: https://github.com/canonical/operator/commit/34e5dfb08251295c96cbfd3976c78b41c991b4ec

Revision history for this message
Tianqi Xiao (txiao) :
Revision history for this message
Andrea Ieri (aieri) wrote :

lgtm

review: Approve
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paul Goins (vultaire) wrote :

Basically it seems OK - although I'm concerned regarding the removal of the BlockedStatuses does jump out. I presume there's been some discussion about this?

My feeling is that simply logging errors without any status visible via the model is likely to result in silent problems that engineers are less likely to notice - that's why I'm concerned. But I also see these statuses were very deliberately removed.

Can you explain the "why" here?

review: Needs Information
Revision history for this message
Tianqi Xiao (txiao) wrote (last edit ):

> Basically it seems OK - although I'm concerned regarding the removal of the
> BlockedStatuses does jump out. I presume there's been some discussion about
> this?
>
> My feeling is that simply logging errors without any status visible via the
> model is likely to result in silent problems that engineers are less likely to
> notice - that's why I'm concerned. But I also see these statuses were very
> deliberately removed.
>
> Can you explain the "why" here?

Thanks for your review.

Yes, they are deliberately removed after some discussions with Andrea and the Charm team (partial convo here[1]). The reason is that charm, by definition, should just be a wrapper of the actual payload, therefore its status should only reflect the state of the automation process, not of its underlying services. Monitoring services should be responsible for catching abnormalities for the latter.

Additionally, in the discovery and login case specifically, there are many reasons why it would fail and not all of them can be resolved in the charm (e.g. iscsi target is dead or mis-configured). So setting a BlockedStatus in such a case without having a way to unblock it from the charm side is a bit counter-intuitive.

Therefore, we decided to remove them and only keeping the ones that can be resolved from the charm side.

[1]: https://chat.charmhub.io/charmhub/pl/cj3f7pb1ktg8jg8hqj7oxk5dph

Revision history for this message
Paul Goins (vultaire) wrote :

OK - that makes sense.

Added one more comment based upon that chat thread.

Revision history for this message
Tianqi Xiao (txiao) wrote :

I have pushed the changes accordingly. Thanks :)

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paul Goins (vultaire) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/config.yaml b/config.yaml
index c283b6f..c962a7f 100644
--- a/config.yaml
+++ b/config.yaml
@@ -21,10 +21,11 @@ options:
21 type: boolean21 type: boolean
22 default: True22 default: True
23 description: |23 description: |
24 If set to True (default) and storage-type is iscsi, the charm will configure24 If set to True (default) and storage-type is iscsi, the charm will run an
25 the iscsid and multipath configuration files, and then run an iscsiadm25 iscsiadm discovery and login against the target after charm-managed iscsi
26 discovery and login against the target. If set to False, the charm will simply26 services restart (triggered by either config changes or by the restart-services
27 configure the unit but not run the discovery and login against the target.27 action). If set to False, the charm will not automatically run the discovery
28 and login against the target.
28 initiator-dictionary:29 initiator-dictionary:
29 type: string30 type: string
30 default: '{}'31 default: '{}'
diff --git a/requirements.txt b/requirements.txt
index 814be4a..49af55c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,5 @@
1jinja21jinja2
2ops
3charmhelpers
4\ No newline at end of file2\ No newline at end of file
3charmhelpers
4
5# Pin ops version to support bionic deployments
6ops < 2.0
5\ No newline at end of file7\ No newline at end of file
diff --git a/src/charm.py b/src/charm.py
index 8f680a4..21ebdb2 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -351,17 +351,21 @@ class StorageConnectorCharm(CharmBase):
351 except subprocess.CalledProcessError:351 except subprocess.CalledProcessError:
352 logging.exception('An error occured while restarting %s.', service)352 logging.exception('An error occured while restarting %s.', service)
353353
354 # Clear deferred restart events
354 deferred_events.clear_deferred_restarts(services)355 deferred_events.clear_deferred_restarts(services)
355356
357 # If any iscsi services restarted and iscsi-discovery-and-login config is
358 # set to true, run iscsiadm discovery and login.
359 if (any(svc in self.ISCSI_SERVICES for svc in services) and
360 self.model.config.get('iscsi-discovery-and-login')):
361 self._iscsi_discovery_and_login()
362
356 def _reload_multipathd_service(self):363 def _reload_multipathd_service(self):
357 """Reload multipathd service."""364 """Reload multipathd service."""
358 logging.info('Reloading multipathd service')365 logging.info('Reloading multipathd service')
359 try:366 try:
360 subprocess.check_call(['systemctl', 'reload', self.MULTIPATHD_SERVICE])367 subprocess.check_call(['systemctl', 'reload', self.MULTIPATHD_SERVICE])
361 except subprocess.CalledProcessError:368 except subprocess.CalledProcessError:
362 self.unit.status = BlockedStatus(
363 "An error occured while reloading the multipathd service."
364 )
365 logging.exception(369 logging.exception(
366 '%s',370 '%s',
367 "An error occured while reloading the multipathd service."371 "An error occured while reloading the multipathd service."
@@ -374,8 +378,6 @@ class StorageConnectorCharm(CharmBase):
374 charm_config = self.model.config378 charm_config = self.model.config
375 if charm_config.get('enable-auto-restarts') or not self._stored.started:379 if charm_config.get('enable-auto-restarts') or not self._stored.started:
376 self._restart_services(services=self.ISCSI_SERVICES)380 self._restart_services(services=self.ISCSI_SERVICES)
377 if charm_config.get('iscsi-discovery-and-login'):
378 self._iscsi_discovery_and_login()
379 else:381 else:
380 self._defer_service_restart(382 self._defer_service_restart(
381 services=self.ISCSI_SERVICES,383 services=self.ISCSI_SERVICES,
@@ -533,9 +535,6 @@ class StorageConnectorCharm(CharmBase):
533 '-p', target + ':' + port])535 '-p', target + ':' + port])
534 except subprocess.CalledProcessError:536 except subprocess.CalledProcessError:
535 logging.exception('Iscsi discovery failed.')537 logging.exception('Iscsi discovery failed.')
536 self.unit.status = BlockedStatus(
537 'Iscsi discovery failed against target'
538 )
539 return538 return
540539
541 try:540 try:
@@ -545,13 +544,6 @@ class StorageConnectorCharm(CharmBase):
545 except subprocess.CalledProcessError as err:544 except subprocess.CalledProcessError as err:
546 logging.exception('Iscsi login failed. \n%s', err.output.decode("utf-8"))545 logging.exception('Iscsi login failed. \n%s', err.output.decode("utf-8"))
547546
548 # Check if exception occurred because session already present.
549 # No error if it is, otherwise set block status.
550 if "already present" not in err.output.decode("utf-8"):
551 self.unit.status = BlockedStatus(
552 'Iscsi login failed against target'
553 )
554
555 def _fc_scan_host(self):547 def _fc_scan_host(self):
556 hba_adapters = subprocess.getoutput('ls /sys/class/scsi_host')548 hba_adapters = subprocess.getoutput('ls /sys/class/scsi_host')
557 logging.debug('hba_adapters: {}'.format(hba_adapters))549 logging.debug('hba_adapters: {}'.format(hba_adapters))
diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
index 37f78a5..18daa18 100644
--- a/tests/unit/test_charm.py
+++ b/tests/unit/test_charm.py
@@ -13,7 +13,7 @@ import charm
13import charmhelpers.contrib.openstack.deferred_events as deferred_events13import charmhelpers.contrib.openstack.deferred_events as deferred_events
1414
15from ops.framework import EventBase15from ops.framework import EventBase
16from ops.model import ActiveStatus, BlockedStatus16from ops.model import ActiveStatus
17from ops.testing import Harness17from ops.testing import Harness
1818
1919
@@ -228,8 +228,10 @@ class TestCharm(unittest.TestCase):
228 action_event.results['failed'], 'No valid services are specified.')228 action_event.results['failed'], 'No valid services are specified.')
229229
230 @patch("charm.subprocess.check_call")230 @patch("charm.subprocess.check_call")
231 def test_on_restart_services_actionservices_success(self,231 @patch("charm.StorageConnectorCharm._iscsi_discovery_and_login")
232 m_check_call):232 def test_on_restart_services_action_services_success(self,
233 m_iscsi_discovery_and_login,
234 m_check_call):
233 """Test on restart servcices action successfully run with services param."""235 """Test on restart servcices action successfully run with services param."""
234 action_event = FakeActionEvent(params={236 action_event = FakeActionEvent(params={
235 "deferred-only": False, "services": "iscsid open-iscsi multipathd"})237 "deferred-only": False, "services": "iscsid open-iscsi multipathd"})
@@ -243,6 +245,7 @@ class TestCharm(unittest.TestCase):
243 any_order=True245 any_order=True
244 )246 )
245 self.assertEqual(action_event.results['success'], 'True')247 self.assertEqual(action_event.results['success'], 'True')
248 m_iscsi_discovery_and_login.assert_called_once()
246249
247 @patch("charm.deferred_events.get_deferred_restarts")250 @patch("charm.deferred_events.get_deferred_restarts")
248 def test_on_show_deferred_restarts_action(self, m_get_deferred_restarts):251 def test_on_show_deferred_restarts_action(self, m_get_deferred_restarts):
@@ -370,9 +373,64 @@ class TestCharm(unittest.TestCase):
370 any_order=False373 any_order=False
371 )374 )
372375
376 @patch("charm.subprocess.check_call")
377 @patch("charm.StorageConnectorCharm._iscsi_discovery_and_login")
378 def test_on_restart_non_iscsi_services(self,
379 m_iscsi_discovery_and_login,
380 m_check_call):
381 """Test on restarting non-iscsi services."""
382 self.harness.charm._restart_services(services=["multipathd"])
383 m_check_call.assert_has_calls(
384 [
385 call(["systemctl", "restart", "multipathd"])
386 ],
387 any_order=True
388 )
389 m_iscsi_discovery_and_login.assert_not_called()
390
391 @patch("charm.subprocess.check_call")
392 @patch("charm.StorageConnectorCharm._iscsi_discovery_and_login")
393 def test_on_restart_iscsi_services_with_discovery_login(
394 self,
395 m_iscsi_discovery_and_login,
396 m_check_call
397 ):
398 """Test on restarting a iscsi service with discovery and login."""
399 self.harness.update_config({'iscsi-discovery-and-login': True})
400 self.harness.charm._restart_services(services=["iscsid"])
401 m_check_call.assert_has_calls(
402 [
403 call(["systemctl", "restart", "iscsid"])
404 ],
405 any_order=True
406 )
407 m_iscsi_discovery_and_login.assert_called_once()
408
409 @patch("charm.subprocess.check_call")
410 @patch("charm.StorageConnectorCharm._iscsi_discovery_and_login")
411 def test_on_restart_iscsi_services_without_discovery_login(
412 self,
413 m_iscsi_discovery_and_login,
414 m_check_call
415 ):
416 """Test on restarting a iscsi service without running discovery and login."""
417 self.harness.update_config({'iscsi-discovery-and-login': False})
418 self.harness.charm._restart_services(services=["iscsid"])
419 m_check_call.assert_has_calls(
420 [
421 call(["systemctl", "restart", "iscsid"])
422 ],
423 any_order=True
424 )
425 m_iscsi_discovery_and_login.assert_not_called()
426
373 @patch("charm.subprocess.check_output")427 @patch("charm.subprocess.check_output")
374 @patch("charm.subprocess.check_call")428 @patch("charm.subprocess.check_call")
375 def test_iscsiadm_discovery_failed(self, m_check_call, m_check_output):429 @patch("charm.logging.exception")
430 def test_iscsiadm_discovery_failed(self,
431 m_log_exception,
432 m_check_call,
433 m_check_output):
376 """Test response to iscsiadm discovery failure."""434 """Test response to iscsiadm discovery failure."""
377 self.harness.update_config({435 self.harness.update_config({
378 "storage-type": "iscsi",436 "storage-type": "iscsi",
@@ -386,14 +444,15 @@ class TestCharm(unittest.TestCase):
386 '-p', "abc" + ':' + "443"],444 '-p', "abc" + ':' + "443"],
387 )445 )
388 self.harness.charm._iscsi_discovery_and_login()446 self.harness.charm._iscsi_discovery_and_login()
389 self.assertEqual(447 m_log_exception.assert_called_once()
390 self.harness.charm.unit.status,
391 BlockedStatus('Iscsi discovery failed against target')
392 )
393448
394 @patch("charm.subprocess.check_output")449 @patch("charm.subprocess.check_output")
395 @patch("charm.subprocess.check_call")450 @patch("charm.subprocess.check_call")
396 def test_iscsiadm_login_failed(self, m_check_call, m_check_output):451 @patch("charm.logging.exception")
452 def test_iscsiadm_login_failed(self,
453 m_log_exception,
454 m_check_call,
455 m_check_output):
397 """Test response to iscsiadm login failure."""456 """Test response to iscsiadm login failure."""
398 self.harness.update_config({457 self.harness.update_config({
399 "storage-type": "iscsi",458 "storage-type": "iscsi",
@@ -407,33 +466,7 @@ class TestCharm(unittest.TestCase):
407 output=b'iscsiadm: Could not log into all portals'466 output=b'iscsiadm: Could not log into all portals'
408 )467 )
409 self.harness.charm._iscsi_discovery_and_login()468 self.harness.charm._iscsi_discovery_and_login()
410 self.assertEqual(469 m_log_exception.assert_called_once()
411 self.harness.charm.unit.status,
412 BlockedStatus('Iscsi login failed against target')
413 )
414
415 @patch("charm.subprocess.check_output")
416 @patch("charm.subprocess.check_call")
417 def test_iscsiadm_login_exist_session(self, m_check_call, m_check_output):
418 """Test response to iscsiadm login to exist session."""
419 self.harness.update_config({
420 "storage-type": "iscsi",
421 "iscsi-target": "abc",
422 "iscsi-port": "443"
423 })
424 self.harness.charm.unit.status = ActiveStatus("Unit is ready")
425 m_check_output.side_effect = charm.subprocess.CalledProcessError(
426 returncode=15,
427 cmd=['iscsiadm', '-m', 'node', '--login'],
428 output=b'iscsiadm: default: 1 session requested, ' +
429 b'but 1 already present.\n' +
430 b'iscsiadm: Could not log into all portals'
431 )
432 self.harness.charm._iscsi_discovery_and_login()
433 self.assertEqual(
434 self.harness.charm.unit.status,
435 ActiveStatus("Unit is ready")
436 )
437470
438 @patch("storage_connector.metrics_utils.uninstall_exporter")471 @patch("storage_connector.metrics_utils.uninstall_exporter")
439 @patch("storage_connector.metrics_utils.install_exporter")472 @patch("storage_connector.metrics_utils.install_exporter")

Subscribers

People subscribed via source and target branches

to all changes: