Environments in Opereto

An environment in Opereto is simply a json structure listing a set of entities or attributes. Environments may contain a list of remote existing agents given each agent a logical name or role. However, environments may also be just a list of attributes or credentials of a given remote SaaS application, not involving agents at all.


Example 1: Remote cloud-based test environment (no agents)

{
    "cluster_name": "k8s-clusbbe9",
    "config_file": {
        "contexts": [
            {
                "name": "my-context",
                "context": {
                    "cluster": "k8s-clusbbe9",
                    "user": "my-automation-user"
                }
            }
        ],
        "clusters": [
            {
                "cluster": {
                    "certificate-authority-data": "L3VSSElJZ3ozMFR3Z2xYSjc0UTZkUHNWd0MwVzlDdXZnWAp2eWpWRW93bGxTYit3QUxnZ0w2OFNNSmZuZUVhaDR4d3BCUWNKbWdHZnp2L0N6MWVvNmZZZXdwTWxSb09rYXhVCjllTFdXeU5DQmJoWGZxU0JYRjBjenk4b3hYdHRsWnRoQVlKUVhPUTV3RzVMSmw0ZjVDTFhrLzcyRmJtTUpXTjAKSVlCeUlmYzZLRWFHK25xZkt3NkdMUExyYlJZTTBWdHppUVMreGRzaTNYOUVQblNlaGVMQzU3b3c1VWlJWDBZcQo3MDdzZzdScE=",
                    "server": "https://1.1.1.1"
                },
                "name": "k8s-clusbbe9"
            }
        ],
        "users": [
            {
                "user": {
                    "username": "admin",
                    "password": "4KuIs0tgGWb0kJ3d"
                },
                "name": "my-admin"
            }
        ],
        "current-context": "admin-context"
    },
    "dashboard_token": "cnZpY2UtYWNjb3VudC51aWQiOiJjZDg3NWZiMS03NTUxLTExZTgtYTE1Yy00MjAxMGE4ZTAyMjAiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.GeYRjB_R7mRQ5jJa1nA0bedICPtxw5313C8bYyuPGd3iJINFMO5IzK2mVSMpjq0r8Jmb2w8uY7x67pJhNvPF8PrmPuVE",
    "zone": "us-east1-b"
}


Example 2:  Some SUT cluster with agent installed on each cluster member

{
    "Node1": {
        "cloud_provider": "aws",
        "ip_addr": "10.0.0.1",
        "region": "us-west-2",
        "agent_id": "test5d5d6bdc_Node1",
        "os_platform": "xenial-amd64",
        "id": "i-08fd469b495982d61"
    },
    "Node2": {
        "cloud_provider": "aws",
        "ip_addr": "10.0.0.2",
        "region": "us-west-2",
        "agent_id": "test5d5d6bdc_Node2",
        "os_platform": "xenial-amd64",
        "id": "i-08edf7cf38b793ccc"
    },
    "Node3": {
        "cloud_provider": "aws",
        "ip_addr": "10.0.0.3",
        "region": "us-west-2",
        "agent_id": "test5d5d6bdc_Node2",
        "os_platform": "xenial-amd64",
        "id": "i-0e540803869550140"
    }
}


Example 3: A container test tool with agent installed

{
    "Container": {
        "container_id": "1d9410b5960cef62e55395f270fb71b421a592bda094edf497f81681033bb5fa",
        "container_agent": "dockereto-8d0f8074-490",
        "agent_id": "dockereto-8d0f8074-490",
        "host_agent": "execution-worker-1"
    }
}



Environments <> Agents relations

As you can see in the examples above, the key agent_id is used to keep the agent identifiers for environments containing entities with Opereto agent installed. This key is a built-in directive that tells opereto to relate those agents with a given environment. The JSON of the third example, for instance, includs an entity called "Container" that is related to an agent with identifier "dockereto-8d0f8074-490". 


It is important to mention that if we define relations between s given environment to one or more agents, those agents must exist in Opereto system, otherwise, we will get an exception during creation. The agents may or may not be online. In case that one or more agents related to a given environment are offline, the environment will appear as offline too in Opereto. In other words, environments with agents will be considered "online" if and only if ALL related agents are online.


Opereto indicated the environment-agent relations in both agents and environment UI screens as the follows:

 



Since environments and agents are separate entities related via the agent_id directive, it is clear that any set of agents may be grouped together as an environment as the following chart indicates. This flexibility is very powerful when designing and building distributed system test automation. 


 




Topologies - environment schemes

As you can learn from the examples above, except of the special agent_id directive, the environment JSON scheme is very flexible and it is up to the developer building any environment setup and teardown services to decide how to structure the environment JSON and what data to include in it. However, in many times, we do want to put constraints on this data so that anyone creates/modifies environments must act according to those constraints. If, for instance, our environment is AWS based, we might want to ensure that the region id, vpc_id etc. are included. We may also want to ensure that specific roles are included as well like Node1, Node2 etc. 


Topologies are the scheme specifications of environments. Each environment is a JSON implementation of a given JSON scheme or topology. A topology specification is based on JSON Schema. If you are not familiar with JSON Schema, we recommend checking out the following links (anyhow, the web is full with examples and info about):


The most basic topology scheme is:

{}

It allows any JSON structure (including empty one). Any of the environment JSONs listed above would match this topology scheme.


Let us build now a topology that enforces the second example. The rules we want to enforce are:

  1. Make sure that in the root level we only have keys of the form "Node" followed by an integer
  2. Make sure that each Node contains at least the following attributes:
    • "cloud_provider": value from a predefined cloud provider
    • "ip_addr": a valid ip address 
    • "region": predefined list of null
    • "agent_id": a string matching the opereto agent names policy 
    • "os_platform": representing known os platforms from a pre-defined list
    • "id": string


The topology scheme satisfying the above:

{
    "additionalProperties": false,
    "type": "object",
    "patternProperties": {
        "^Node\\d+$": {
            "additionalProperties": true,
            "required": [
                "id",
                "os_platform",
                "agent_id",
                "region",
                "cloud_provider",
                "ip_addr"
            ],
            "type": "object",
            "properties": {
                "cloud_provider": {
                    "enum": [
                        "aws",
                        "azure",
                        "gcp"
                    ],
                    "type": "string"
                },
                "ip_addr": {
                    "pattern": "^\\d+\\.\\d+\\.\\d+\\.\\d+)$",
                    "type": "string"
                },
                "region": {
                    "enum": [
                        "us-east-1",
                        "us-west-1"
                    ],
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "agent_id": {
                    "minLength": 3,
                    "type": "string",
                    "pattern": "^[a-z]{1}[a-zA-Z0-9-_]{2,126}$"
                },
                "os_platform": {
                    "enum": [
                        "xenial-amd64",
                        "trusty-amd64"
                    ],
                    "type": "string"
                },
                "id": {
                    "type": "string"
                }
            }
        }
    }
}



Create, modify and delete topologies 


Create topology from UI 


*     To modify / delete after creation, select the topology from the topologies grid and click on "Modify Topology" or "Delete Topology" buttons respectively 

**   See the REST API docs to manage topologies programatically

*** Valid topology names:

  • 3-128 characters
  • Stats with small letter
  • Allowed characters are: a-zA-z0-9_-



Create, modify and delete environments

It is clear by now that a given environment is related to a single topology. Thus, prior to environment creation, we have to create a topology entity to represent all environment instances of that topology. In most software organisations, there are a few common test topologies that represent actual use cases of selected customers or customer behaviours. Besides, testing tools are managed in opereto the same way as as test environments and require topology specification too. Whereas topologies are created and modified from time to times, environments are being created and deleted all the time by continuous test cycles. Thus, creating environments via UI is a rare use case and suitable only for permanent environments that are less dynamic and or environments that are hard to setup automatically (e.g hardware based setups, remote third-party SaaS). 


The following examples illustrates how to manipulate environments programatically using pyopereto (see the environments API if you want to use any other rest client):


create environment

client = OperetoClient()

topology_data = {
    "container_host_agent": "execution-worker-1",
    "container_id": "1d9410b5960cef62e55395f270fb71b421a592bda094edf497f81681033bb5fa",
    "container": "k8s_gcp"
}
client.create_environment('my_topology_id', topology=topology_data, id='testenv123', permissions={'users': ['dror'], 'owners': ['dror']})


modify environment

client = OperetoClient()

topology_data = {
    "container_host_agent": "execution-worker-1",
    "container_id": "1d9410b5960cef62e55395f270fb71b421a592bda094edf497f81681033bb5fa",
    "container": "k8s_gcp",
    "cpu_usage": "30"
}

## modify topology data (add cpu usage dynamically, from a test for instance..)
client.modify_environment('testenv123', topology=topology_data)

## modify name
client.modify_environment('testenv123', name='my new environment name')

## modify description
client.modify_environment('testenv123', description='this environments represents a basic user setup...')

## modify permissions
client.modify_environment('testenv123', permissions={'users': ['dror'], 'owners': ['dror']})

*    Environment permissions have the same logic as agent permissions. Users can use, owners may also modify and delete.             

**  Valid environment names:

  • 3-128 characters
  • Stats with small letter
  • Allowed characters are: a-zA-z0-9_-



delete environment 

client = OperetoClient()

client.delete_environment('my_topology_id')



Auto teardown of environments

This feature allows to specify a teardown service and expiration time per environment so that when time had expired, the teardown service is invoked. The teardown specification can also be modified while environment is running.

This mechanism is very useful for manual testers that create cloud-based on-demand test environment on a daily basis and want to avoid the bother of remembering to tear them down at the end of the day or after use. 


For CI, it is less relevant as test plans and test cycle manages automatically the setup and the teardown of test environments.


To modify the auto teardown from UI


To create/modify auto teardown via API

client = OperetoClient()

teardown_spec = {
    "service": "teardown_working_cluster",
    "timeout": "2018-07-22 16:12"
}
client.create_environment('my_topology_id', auto_teardown=teardown_spec, topology=.....)



How to use environments in automation services?

In the service.yaml, you can specify environment properties using the property type: environment. as follows:

...
...
item_properties:
## test tool environment
-   direction: input
    key: tc_client
    mandatory: true
    store:
        environment_types: testcafe_client
    type: environment
    value: null

## the software under test environment 
-   direction: input
    key: sut_environment
    mandatory: true
    store:
        environment_types: my_topology_id               ## a comma delimited topology identifiers instruct Opereto to accept any environment related to those topologies as a valid one
    type: environment
    value: null
....
....
....


In the service code, you can fetch the actual environment JSON at runtime as follows:

client = OperetoClient()
....
....

testcafe_tool_env = client.get_environment(self.input.get('tc_client'))['topology']
sut_env = client.get_environment(self.input.get('sut_environment'))['topology']

....
....