diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..500c7db --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +python: +- '2.7' +install: +- pip install nose +- pip install coveralls +- pip install -r src/requirements.txt +script: +- nosetests --include it_ +after_success: coveralls diff --git a/scripts/docker_deployment.py b/scripts/docker_deployment.py index 0ce4fc5..d3bd7c6 100644 --- a/scripts/docker_deployment.py +++ b/scripts/docker_deployment.py @@ -1,9 +1,12 @@ -import cloudshell.api.cloudshell_scripts_helpers as helpers +import cloudshell.helpers.scripts.cloudshell_scripts_helpers as helpers from cloudshell.api.cloudshell_api import * import os +import json + reservation_id = helpers.get_reservation_context_details().id service_details = helpers.get_resource_context_details() +dict = helpers.get_resource_context_details_dict() docker_host = service_details.attributes['Docker Host'] image = service_details.attributes['Docker Image'] environment = service_details.attributes['Container Env'] @@ -12,10 +15,11 @@ api = helpers.get_api_session() -api.WriteMessageToReservationOutput(reservation_id,"Starting deploy") +api.WriteMessageToReservationOutput(reservation_id,"Deploying App..." ) result = api.ExecuteCommand(reservationId=reservation_id, targetName=docker_host, targetType="Resource", commandName="deploy_image", - commandInputs=[InputNameValue("image", image),InputNameValue("env", environment), + commandInputs=[InputNameValue("app_name",dict["appData"]["name"]), + InputNameValue("image", image),InputNameValue("env", environment), InputNameValue("port_config", ports)]) api.WriteMessageToReservationOutput(reservation_id,"Deploy done:" + result.Output) diff --git a/src/dockerhostdriver.py b/src/dockerhostdriver.py index 0436c16..f818480 100755 --- a/src/dockerhostdriver.py +++ b/src/dockerhostdriver.py @@ -3,10 +3,10 @@ import requests from cloudshell.api.cloudshell_api import CloudShellAPISession, ResourceAttributesUpdateRequest, AttributeNameValue from cloudshell.shell.core.resource_driver_interface import ResourceDriverInterface - +import uuid class DockerHostDriver (ResourceDriverInterface): - def __init__(self): + def __init__(self, domain_logic): pass # Initialize the driver session, this function is called everytime a new instance of the driver is created @@ -42,24 +42,33 @@ def destroy(self, context, ports): :return The JSON response and HTTP status code :rtype str """ - vm_info_json =context.remote_endpoints[0].app_context.deployed_app_json - vm_info_obj = json.loads(vm_info_json) - uid = vm_info_obj['vmdetails']['uid'] - log = "" - address = context.resource.address - response = requests.post('{address}/containers/{uid}/stop'.format(address=address, uid=uid) ) - log+=str(response.status_code) + ": " + response.content - response = requests.delete('{address}/containers/{uid}'.format(address=address, uid=uid) ) - log = log + '\n' + str(response.status_code) + ": " + response.content + log = self.destroy_vm_only(context,ports) session = CloudShellAPISession(host=context.connectivity.server_address,token_id=context.connectivity.admin_auth_token,domain='Global') session.DeleteResource(context.remote_endpoints[0].name) return log - def deploy_image(self, context, image, env, port_config): + + + def _get_vm_uui(self, context): + vm_info_json = context.remote_endpoints[0].app_context.deployed_app_json + vm_info_obj = json.loads(vm_info_json) + uid = vm_info_obj['vmdetails']['uid'] + return uid + + def _get_remote_name(self, context): + """ + :type context: cloudshell.shell.core.driver_context.ResourceRemoteCommandContext + + """ + return context.remote_endpoints[0].name + + def deploy_image(self, context, app_name, image, env, port_config): """ Deploys a container from an image :param context: This is the execution context automatically injected by CloudShell when running this command :type context: cloudshell.shell.core.driver_context.ResourceCommandContext + :param app_name: The name of the app being deployed + :type app_name: str :param image: The docker image to create the container from :type image: str :param env: Environment variables to use for the container, provided as comma separated list of name=value @@ -77,7 +86,7 @@ def deploy_image(self, context, image, env, port_config): address = context.resource.address response = None try: - response = requests.post('{address}:4000/containers/create'.format(address=address), + response = requests.post('{address}/containers/create'.format(address=address), create_request_data) except Exception as e: @@ -88,10 +97,31 @@ def deploy_image(self, context, image, env, port_config): "response" + response.content) container_id = response.json()["Id"] + app_unique_name = app_name + "_" + str(uuid.uuid4())[0:4] + result = '{ "vm_name" : "%s", "vm_uuid" : "%s", "cloud_provider_resource_name" : "%s"}' % (app_unique_name ,container_id, context.resource.name) - str = '{ "vm_name" : "%s", "vm_uuid" : "%s", "cloud_provider_resource_name" : "%s"}' % (image.replace('/','_').replace(':','_') ,container_id, context.resource.name) + return json.loads(result) + + + + def destroy_vm_only(self, context, ports): + """ + Destroys only the containers (without the associated CloudShell resource) For internal purposes only + :param context: This is the execution context automatically injected by CloudShell when running this command + :type context: cloudshell.shell.core.driver_context.ResourceRemoteCommandContext + :param ports: OBSOLETE - information on the remote ports + :type ports: str + + """ + uid = self._get_vm_uui(context) + log = "" + address = context.resource.address + response = requests.post('{address}/containers/{uid}/stop'.format(address=address, uid=uid)) + log += str(response.status_code) + ": " + response.content + response = requests.delete('{address}/containers/{uid}'.format(address=address, uid=uid)) + log = log + '\n' + str(response.status_code) + ": " + response.content + return log - return json.loads(str) # the name is by the Qualisystems conventions def remote_refresh_ip(self, context, ports): @@ -105,6 +135,7 @@ def remote_refresh_ip(self, context, ports): """ json = self.inspect(context,ports) ip = json["Node"]["IP"] + matching_resources = \ self._get_api_session(context).FindResources(attributeValues=[AttributeNameValue("Private IP", ip)])\ .Resources @@ -145,12 +176,9 @@ def _get_api_session(self, context): port=context.connectivity.cloudshell_api_port) - def inspect(self, context, ports ): address = context.resource.address - vm_info_json =context.remote_endpoints[0].app_context.deployed_app_json - vm_info_obj = json.loads(vm_info_json) - uid = vm_info_obj['vmdetails']['uid'] + uid = self._get_vm_uui(context) response = requests.get('{address}/containers/{uid}/json'.format(address=address, uid=uid) ) result = response.json() return result @@ -159,13 +187,11 @@ def inspect(self, context, ports ): # the name is by the Qualisystems conventions def show_logs(self, context, ports): address = context.resource.address - vm_info_json =context.remote_endpoints[0].app_context.deployed_app_json - vm_info_obj = json.loads(vm_info_json) - uid = vm_info_obj['vmdetails']['uid'] + uid = self._get_vm_uui(context) response = requests.get('{address}/containers/{uid}/logs?stdout=1'.format(address=address, uid=uid) ) return response.content - def power_on(self, context, vm_uuid, resource_fullname): + def power_on(self, context, vm_uuid): uid = vm_uuid log = "" address = context.resource.address @@ -174,6 +200,30 @@ def power_on(self, context, vm_uuid, resource_fullname): log+=str(response.status_code) + ": " + response.content return log + + def PowerOn(self, context, ports): + """ + Powers off the remote vm + :param models.QualiDriverModels.ResourceRemoteCommandContext context: the context the command runs on + :param list[string] ports: the ports of the connection between the remote resource and the local resource, NOT IN USE!!! + """ + uid = self._get_vm_uui(context) + self.power_on(context,uid) + self._get_api_session(context).SetResourceLiveStatus( self._get_remote_name(context),'Online') + + + + def PowerOff(self, context, ports): + uid = self._get_vm_uui(context) + address = context.resource.address + requests.post('{address}/containers/{uid}/stop'.format(address=address, uid=uid)) + self._get_api_session(context).SetResourceLiveStatus( self._get_remote_name(context),'Offline') + + + # the name is by the Qualisystems conventions + def PowerCycle(self, context, ports, delay): + pass + def _wrapInParenthesis(self, value): value = value.strip() if not value.startswith('"'): diff --git a/src/drivermetadata.xml b/src/drivermetadata.xml index 2576996..7397d32 100755 --- a/src/drivermetadata.xml +++ b/src/drivermetadata.xml @@ -3,39 +3,37 @@ - - + + + + + - - - - - - + + - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + - - - + + + diff --git a/src/refresh_ip_request.py b/src/refresh_ip_request.py new file mode 100644 index 0000000..661319b --- /dev/null +++ b/src/refresh_ip_request.py @@ -0,0 +1,3 @@ +class RefreshIpRequest: + def __init__(self,resource_name): + self.resource_name = resource_name diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/docker_driver_tests.py b/tests/docker_driver_tests.py new file mode 100644 index 0000000..6476b42 --- /dev/null +++ b/tests/docker_driver_tests.py @@ -0,0 +1,61 @@ +from unittest import TestCase +from mock import Mock +from dockerhostdriver import DockerHostDriver +from refresh_ip_request import RefreshIpRequest +from cloudshell.shell.core.context import ResourceCommandContext + + +class CloudShellContextObjectStub(object): + def __init__(self, resource_name): + self.resource = "" + + +class DockerNewDriver(object): + def __init__(self, docker_api_service, cloudshell_service): + self.docker_api_service = docker_api_service + self.cloudshell_service=cloudshell_service + + def refresh_ip(self): + ip = self.docker_api_service.get_container_ip() + self.cloudshell_service.set_resource_address(ip) + + +class StubDockerApiService: + + def get_container_ip(self): + return self.ip_to_return + + +class DescribeRefreshIPOnDockerHostDriver (TestCase): + + + def it_gets_the_ip_from_the_docker_host(self): + docker_api_service = Mock() + cloudshell_service = Mock() + + driver = DockerNewDriver(docker_api_service,cloudshell_service).refresh_ip() + docker_api_service.get_container_ip.assert_called() + + def it_updates_cloudshell_with_the_ip_it_got_from_the_docker_host(self): + docker_api_service = Mock(spec=StubDockerApiService) + docker_api_service.get_container_ip.return_value="192.12.33.22" + print docker_api_service.get_container_ip() + cloudshell_service = Mock() + + DockerNewDriver(docker_api_service,cloudshell_service).refresh_ip() + cloudshell_service.set_resource_address.assert_called_once_with("192.12.33.22") + + + # + # + # + # + # + # + # def it_converts_the_cloudshell_execution_context_to_a_refresh_ip_request(self): + # mock_domain_logic = Mock() + # driver = DockerHostDriver(mock_domain_logic) + # driver.remote_refresh_ip(context=CloudShellContextObjectStub("resource1"), ports="") + # mock_domain_logic.refresh_ip.assert_called_once_with(RefreshIpRequest("resource1")) + # self.assertTrue(False) +