diff --git a/docs/shared_combiners_catalog/cloud_instance.rst b/docs/shared_combiners_catalog/cloud_instance.rst new file mode 100644 index 0000000000..9b372a7e20 --- /dev/null +++ b/docs/shared_combiners_catalog/cloud_instance.rst @@ -0,0 +1,3 @@ +.. automodule:: insights.combiners.cloud_instance + :members: + :show-inheritance: diff --git a/insights/combiners/cloud_instance.py b/insights/combiners/cloud_instance.py new file mode 100644 index 0000000000..8b50bd4b40 --- /dev/null +++ b/insights/combiners/cloud_instance.py @@ -0,0 +1,96 @@ +""" +Cloud Instance +============== + +Combiner for the basic information of a cloud instance. It combines the +results of the following combiners and parsers: + +* :py:class:`insights.combiners.cloud_provider.CloudProvider` +* :py:class:`insights.parsers.aws_instance_id.AWSInstanceIdDoc` +* :py:class:`insights.parsers.azure_instance.AzureInstanceID` +* :py:class:`insights.parsers.azure_instance.AzureInstanceType` +* :py:class:`insights.parsers.gcp_instance_type.GCPInstanceType` +* :py:class:`insights.parsers.subscription_manager.SubscriptionManagerFacts` + +""" +from insights import SkipComponent +from insights.core.plugins import combiner, ContentException +from insights.parsers.aws_instance_id import AWSInstanceIdDoc +from insights.parsers.azure_instance import AzureInstanceID, AzureInstanceType +from insights.parsers.gcp_instance_type import GCPInstanceType +from insights.parsers.subscription_manager import SubscriptionManagerFacts +from insights.combiners.cloud_provider import CloudProvider + + +# "google" is used in class:`insights.combiners.cloud_provider.CloudProvider`. +# but 'gcp' is outputted in "subscription-manager facts" +rhsm_type_maps = { + "google": 'gcp' +} + + +@combiner( + CloudProvider, + [ + AWSInstanceIdDoc, + AzureInstanceID, + AzureInstanceType, + GCPInstanceType, + SubscriptionManagerFacts, + ] +) +class CloudInstance(object): + """ + Class to provide the basic information of a cloud instance. + + Attributes: + provider (str): The cloud provider, e.g. "aws", "azure", "ibm", + "google", or "alibaba". It's from the value of + :class:`insights.combiners.cloud_provider.CloudProvider.cloud_provider` + id (str): The ID of the cloud instance + type (str): The type of the cloud instance. + Different cloud providers have different illustration of the + `type` and `size`, here for this Combiner, we treat the `type` and + `size` as the same. E.g.:: + + - "Standard_L64s_v2" for Azure + - "x1.16xlarge" for AWS + - "m1-megamem-96" for GCP + + size (str): Alias of the `type` + + Examples: + >>> ci.provider + 'aws' + >>> ci.id == 'i-1234567890abcdef0' + True + >>> ci.type == 't2.micro' + True + >>> ci.size == 't2.micro' + True + """ + def __init__(self, cp, aws=None, azure_id=None, azure_type=None, + gcp=None, facts=None): + self.provider = cp.cloud_provider + self.id = None + # 1. Get from the Cloud REST API at first + if aws: + self.id = aws.get('instanceId') + self.type = aws.get('instanceType') + elif azure_id and azure_type: + self.id = azure_id.id + self.type = azure_type.raw + elif gcp: + self.type = gcp.raw + # 2. Check the "subscription-manager facts" + if self.id is None and facts: + cp_name = rhsm_type_maps.get(self.provider, self.provider) + key = "{0}_instance_id".format(cp_name) + if key not in facts: + raise ContentException("Unmatched/unsupported types!") + self.id = facts[key] + # The instance id is the key attribute of this Combiner + if self.id is None: + raise SkipComponent + # 'size' is the alias of 'type' + self.size = self.type diff --git a/insights/tests/combiners/test_cloud_instance.py b/insights/tests/combiners/test_cloud_instance.py new file mode 100644 index 0000000000..9ca952d728 --- /dev/null +++ b/insights/tests/combiners/test_cloud_instance.py @@ -0,0 +1,82 @@ +import pytest +import doctest +from insights import SkipComponent +from insights.core.plugins import ContentException +from insights.parsers.installed_rpms import InstalledRpms +from insights.parsers.gcp_instance_type import GCPInstanceType +from insights.parsers.aws_instance_id import AWSInstanceIdDoc +from insights.parsers.azure_instance import AzureInstanceID, AzureInstanceType +from insights.parsers.subscription_manager import SubscriptionManagerFacts +from insights.combiners import cloud_instance +from insights.combiners.cloud_provider import CloudProvider +from insights.combiners.cloud_instance import CloudInstance +from insights.tests import context_wrap +from insights.tests.parsers.test_gcp_instance_type import GOOGLE_TYPE_1 +from insights.tests.parsers.test_aws_instance_id import AWS_ID_DOC +from insights.tests.parsers.test_azure_instance import AZURE_ID, AZURE_TYPE_2 +from insights.tests.parsers.test_subscription_manager import INPUT_NORMAL_1 +from insights.tests.combiners.test_cloud_provider import RPMS_AWS, RPMS_GOOGLE, RPMS_AZURE + +GOOGLE_RHSM_FACTS = """ +gcp_instance_id: 567890567890 +network.ipv6_address: ::1 +uname.sysname: Linux +""".strip() + + +def test_cloud_instance_google(): + rpms = InstalledRpms(context_wrap(RPMS_GOOGLE)) + _type = GCPInstanceType(context_wrap(GOOGLE_TYPE_1)) + cp = CloudProvider(rpms, None, None, None) + facts = SubscriptionManagerFacts(context_wrap(GOOGLE_RHSM_FACTS)) + ret = CloudInstance(cp, None, None, None, _type, facts) + assert ret.provider == CloudProvider.GOOGLE + assert ret.id == "567890567890" + assert ret.type == "n2-highcpu-16" + assert ret.size == "n2-highcpu-16" + + +def test_cloud_instance_aws(): + rpms = InstalledRpms(context_wrap(RPMS_AWS)) + _id = AWSInstanceIdDoc(context_wrap(AWS_ID_DOC)) + cp = CloudProvider(rpms, None, None, None) + ret = CloudInstance(cp, _id, None, None, None, None) + assert ret.provider == CloudProvider.AWS + assert ret.id == "i-1234567890abcdef0" + assert ret.type == "t2.micro" + assert ret.size == "t2.micro" + + +def test_cloud_instance_azure(): + rpms = InstalledRpms(context_wrap(RPMS_AZURE)) + _id = AzureInstanceID(context_wrap(AZURE_ID)) + _type = AzureInstanceType(context_wrap(AZURE_TYPE_2)) + cp = CloudProvider(rpms, None, None, None) + ret = CloudInstance(cp, None, _id, _type, None, None) + assert ret.provider == CloudProvider.AZURE + assert ret.id == "f904ece8-c6c1-4b5c-881f-309b50f25e50" + assert ret.type == "Standard_NV48s_v3" + assert ret.size == "Standard_NV48s_v3" + + +def test_cloud_instance_ex(): + rpms = InstalledRpms(context_wrap(RPMS_GOOGLE)) + _type = GCPInstanceType(context_wrap(GOOGLE_TYPE_1)) + cp = CloudProvider(rpms, None, None, None) + aws_facts = SubscriptionManagerFacts(context_wrap(INPUT_NORMAL_1)) + + with pytest.raises(ContentException) as ce: + CloudInstance(cp, None, None, None, None, aws_facts) + assert "Unmatched" in str(ce) + + with pytest.raises(SkipComponent): + CloudInstance(cp, None, None, None, _type, None) + + +def test_cloud_instance_doc(): + rpms = InstalledRpms(context_wrap(RPMS_AWS)) + _id = AWSInstanceIdDoc(context_wrap(AWS_ID_DOC)) + cp = CloudProvider(rpms, None, None, None) + env = {'ci': CloudInstance(cp, _id, None, None, None, None)} + failed, total = doctest.testmod(cloud_instance, globs=env) + assert failed == 0