Skip to content
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
10 changes: 7 additions & 3 deletions scripts/docker_deployment.py
Original file line number Diff line number Diff line change
@@ -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']
Expand All @@ -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)
Expand Down
96 changes: 73 additions & 23 deletions src/dockerhostdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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('"'):
Expand Down
52 changes: 25 additions & 27 deletions src/drivermetadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,37 @@
<Layout>
<Category Name="Management">

<Command Description="" DisplayName="Show logs" Name="show_logs" Tags="remote_management,allow_shared" />
<Command Description="" DisplayName="Inspect" Name="inspect" Tags="remote_management,allow_shared" />
<Command Description="Get stdout and stderr logs from the app container" DisplayName="Show logs" Name="show_logs" Tags="remote_management,allow_shared" />
<Command Description="Get more detailed information about the container" DisplayName="Inspect" Name="inspect" Tags="remote_management,allow_shared" />
<Command Name="destroy" DisplayName="Destroy container" Description="Stop and delete the app" Tags="remote_virtualization,allow_shared"/>
<Command Name="power_on" DisplayName="Power On" Description="an example command" Tags="allow_unreserved">
<Parameters>


<Command Name="deployImage" DisplayName="Deploy Image" Description="an example command" Tags="allow_unreserved">
<Parameters>
<Parameter Name="image" Type="String" Mandatory = "True" DefaultValue="" DisplayName="image" Description=""/>
<Parameter Name="env" Type="String" Mandatory = "True" DefaultValue="" DisplayName="env" Description=""/>
<Parameter Name="port_config" Type="String" Mandatory = "True" DefaultValue="" DisplayName="port_config" Description=""/>
<Parameter Name="vm_uuid" Type="String" Mandatory = "True" DefaultValue="" DisplayName="image" Description=""/>
<Parameter Name="resource_fullname" Type="String" Mandatory = "True" DefaultValue="s" DisplayName="image" Description=""/>

</Parameters>
</Command>

<Command Name="destroy" DisplayName="Destroy container" Description="an example command" Tags="remote_virtualization,allow_shared">
</Command>
<Command Name="power_on" DisplayName="Power On" Description="an example command" Tags="allow_unreserved">
<Parameters>

<Parameter Name="vm_uuid" Type="String" Mandatory = "True" DefaultValue="" DisplayName="image" Description=""/>
<Parameter Name="resource_fullname" Type="String" Mandatory = "True" DefaultValue="" DisplayName="image" Description=""/>

</Parameters>
</Command>
<Command Description="" DisplayName="Refresh Ip" Name="remote_refresh_ip" Tags="remote_connectivity,allow_shared" />

</Command>
</Category>
<Category Name="Connectivity">
<Command Description="" DisplayName="Refresh Ip" Name="remote_refresh_ip" Tags="remote_connectivity,allow_shared" />
<Category Name="Hidden Commands">
<Command Description="Delete the VM only (internal command)" DisplayName="" Name="destroy_vm_only" Tags="remote_app_management,allow_shared" />

<Command Name="deploy_image" DisplayName="Deploy Image" Description="Deploys a docker container from image" Tags="allow_unreserved">
<Parameters>
<Parameter Name="app_name" Type="String" Mandatory = "True" DefaultValue="" DisplayName="app_name" Description=""/>
<Parameter Name="image" Type="String" Mandatory = "True" DefaultValue="" DisplayName="image" Description=""/>
<Parameter Name="env" Type="String" Mandatory = "True" DefaultValue="" DisplayName="env" Description=""/>
<Parameter Name="port_config" Type="String" Mandatory = "True" DefaultValue="" DisplayName="port_config" Description=""/>
</Parameters>
</Command>
<Command Description="Inspects the app and updates its IP address in CloudShell"
DisplayName="Refresh Ip" Name="remote_refresh_ip" Tags="remote_connectivity,allow_shared" />

</Category>
<Category Name="Power">
<Command Description="" DisplayName="Power On" Name="PowerOn" Tags="power" />
<Command Description="" DisplayName="Power Off" Name="PowerOff" Tags="power,allow_shared" />
<Command Description="" DisplayName="Power Cycle" Name="PowerCycle" Tags="power" />
<Command Description="Powers on (starts) the container" DisplayName="Power On" Name="PowerOn" Tags="power" />
<Command Description="Powers off (stops) the container" DisplayName="Power Off" Name="PowerOff" Tags="power,allow_shared" />
<Command Description="Stops and then starts the container again" DisplayName="Power Cycle" Name="PowerCycle" Tags="power" />
</Category>
</Layout>
</Driver>
3 changes: 3 additions & 0 deletions src/refresh_ip_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class RefreshIpRequest:
def __init__(self,resource_name):
self.resource_name = resource_name
Empty file added tests/__init__.py
Empty file.
61 changes: 61 additions & 0 deletions tests/docker_driver_tests.py
Original file line number Diff line number Diff line change
@@ -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)