Merge ssh://gerrit.mirantis.com:29418/keero/keero into feature-orchestration

This commit is contained in:
Stan Lagun
2013-03-05 19:30:26 +04:00
16 changed files with 389 additions and 33 deletions

View File

@@ -35,27 +35,35 @@ def windcclient(request):
% (request.user.token, url)) % (request.user.token, url))
return windc_client.Client(endpoint=url, token=None) return windc_client.Client(endpoint=url, token=None)
def datacenters_create(request, parameters): def datacenters_create(request, parameters):
name = parameters.get('name', '') name = parameters.get('name', '')
return windcclient(request).datacenters.create(name) return windcclient(request).datacenters.create(name)
def datacenters_delete(request, datacenter_id): def datacenters_delete(request, datacenter_id):
return windcclient(request).datacenters.delete(datacenter_id) return windcclient(request).datacenters.delete(datacenter_id)
def datacenters_get(request, datacenter_id): def datacenters_get(request, datacenter_id):
return windcclient(request).datacenters.get(datacenter_id) return windcclient(request).datacenters.get(datacenter_id)
def datacenters_list(request): def datacenters_list(request):
return windcclient(request).datacenters.list() return windcclient(request).datacenters.list()
def services_create(request, datacenter, parameters): def services_create(request, datacenter, parameters):
return windcclient(request).services.create(datacenter, parameters) return windcclient(request).services.create(datacenter, parameters)
def services_list(request, datacenter): def services_list(request, datacenter):
return windcclient(request).services.list(datacenter) return windcclient(request).services.list(datacenter)
def services_get(request, datacenter, service_id): def services_get(request, datacenter, service_id):
return windcclient(request).services.get(datacenter, service_id) return windcclient(request).services.get(datacenter, service_id)
def services_delete(request, datacenter, service_id): def services_delete(request, datacenter, service_id):
return windcclient(request).services.delete(datacenter, service_id) return windcclient(request).services.delete(datacenter, service_id)

View File

@@ -20,19 +20,30 @@
import logging import logging
from django import forms
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from openstack_dashboard import api from openstack_dashboard import api
from horizon import exceptions
from horizon import forms from horizon import forms
from horizon import exceptions
from horizon import messages from horizon import messages
import pdb
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class WizardFormServiceType(forms.Form):
_type = forms.ChoiceField(label=_("Service Type"))
class WizardFormConfiguration(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.CharField(max_length=1)
class UpdateWinDC(forms.SelfHandlingForm): class UpdateWinDC(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput) tenant_id = forms.CharField(widget=forms.HiddenInput)
data_center = forms.CharField(widget=forms.HiddenInput) data_center = forms.CharField(widget=forms.HiddenInput)

View File

@@ -81,8 +81,7 @@ class DeleteDataCenter(tables.BatchAction):
return True return True
def action(self, request, datacenter_id): def action(self, request, datacenter_id):
datacenter = api.windc.datacenters_get(request, datacenter_id) api.windc.datacenters_delete(request, datacenter_id)
api.windc.datacenters_delete(request, datacenter)
class DeleteService(tables.BatchAction): class DeleteService(tables.BatchAction):
@@ -101,9 +100,8 @@ class DeleteService(tables.BatchAction):
link = request.__dict__['META']['HTTP_REFERER'] link = request.__dict__['META']['HTTP_REFERER']
datacenter_id = re.search('windc/(\S+)', link).group(0)[6:-1] datacenter_id = re.search('windc/(\S+)', link).group(0)[6:-1]
############## ##############
datacenter = api.windc.datacenters_get(request, datacenter_id)
api.windc.services_delete(request, datacenter, service_id) api.windc.services_delete(request, datacenter_id, service_id)
class EditService(tables.LinkAction): class EditService(tables.LinkAction):
@@ -116,6 +114,16 @@ class EditService(tables.LinkAction):
return True return True
class Wizard(tables.LinkAction):
name = "wizard"
verbose_name = _("Wizard")
url = "horizon:project:windc:update"
classes = ("ajax-modal", "btn-edit")
def allowed(self, request, instance):
return True
class ShowDataCenterServices(tables.LinkAction): class ShowDataCenterServices(tables.LinkAction):
name = "edit" name = "edit"
verbose_name = _("Services") verbose_name = _("Services")
@@ -144,18 +152,36 @@ class WinDCTable(tables.DataTable):
name = "windc" name = "windc"
verbose_name = _("Windows Data Centers") verbose_name = _("Windows Data Centers")
row_class = UpdateRow row_class = UpdateRow
table_actions = (CreateDataCenter,) table_actions = (CreateDataCenter, Wizard)
row_actions = (ShowDataCenterServices,DeleteDataCenter) row_actions = (ShowDataCenterServices, DeleteDataCenter)
STATUS_DISPLAY_CHOICES = (
("create", "Deploy"),
)
class WinServicesTable(tables.DataTable): class WinServicesTable(tables.DataTable):
name = tables.Column('dc_name', verbose_name=_('Name'))
STATUS_CHOICES = (
(None, True),
("deployed", True),
("active", True),
("error", False),
)
name = tables.Column('dc_name', verbose_name=_('Name'),
link=("horizon:project:windc:service_details"),)
_type = tables.Column('type', verbose_name=_('Type')) _type = tables.Column('type', verbose_name=_('Type'))
status = tables.Column('status', verbose_name=_('Status')) status = tables.Column('status', verbose_name=_('Status'),
status=True,
status_choices=STATUS_CHOICES,
display_choices=STATUS_DISPLAY_CHOICES)
class Meta: class Meta:
name = "services" name = "services"
verbose_name = _("Services") verbose_name = _("Services")
row_class = UpdateRow row_class = UpdateRow
status_columns = ['status']
table_actions = (CreateService,) table_actions = (CreateService,)
row_actions = (EditService, DeleteService) row_actions = (EditService, DeleteService)

View File

@@ -1,2 +1,3 @@
{% load i18n %} {% load i18n %}
<p>{% blocktrans %}You can deploy few domain controllers with one name.{% endblocktrans %}</p> <p>{% blocktrans %}You can deploy few Active Directory services with one domain name.{% endblocktrans %}</p>
<p>{% blocktrans %}The DNS service will automatically created for each Active Directory.{% endblocktrans %}</p>

View File

@@ -0,0 +1,28 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-header %}{% trans "Create Service" %}{% endblock %}
{% block modal-body %}
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
{{ wizard.form.forms }}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" class="btn btn-small" type="submit" value="{{ wizard.steps.prev }}">{% trans "Back" %}</button>
<input type="submit" class="btn btn-primary pull-right" value="{% trans 'Deploy' %}"/>
{% else %}
<button name="wizard_goto_step" class="btn btn-small" type="submit" value="{{ wizard.steps.next }}">{% trans "Next" %}</button>
<input type="submit" class="btn btn-primary pull-right" value="{% trans 'Deploy' %}"/>
{% endif %}
</form>
{% endblock %}

View File

@@ -20,8 +20,10 @@
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from .views import IndexView, CreateWinDCView, WinServices, CreateWinServiceView from .views import IndexView, WinServices, \
CreateWinDCView, CreateWinServiceView
from .views import Wizard
from .forms import WizardFormServiceType, WizardFormConfiguration
VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views' VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views'
@@ -29,6 +31,11 @@ urlpatterns = patterns(VIEW_MOD,
url(r'^$', IndexView.as_view(), name='index'), url(r'^$', IndexView.as_view(), name='index'),
url(r'^create$', CreateWinServiceView.as_view(), name='create'), url(r'^create$', CreateWinServiceView.as_view(), name='create'),
url(r'^create_dc$', CreateWinDCView.as_view(), name='create_dc'), url(r'^create_dc$', CreateWinDCView.as_view(), name='create_dc'),
url(r'^(?P<domain_controller_id>[^/]+)/$', WinServices.as_view(), url(r'^(?P<data_center_id>[^/]+)/$', WinServices.as_view(),
name='services') name='services'),
url(r'^update$',
Wizard.as_view([WizardFormServiceType, WizardFormConfiguration]),
name='update'),
url(r'^(?P<service_id>[^/]+)/$', WinServices.as_view(),
name='service_details')
) )

View File

@@ -15,7 +15,8 @@
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations# under the License. # License for the specific language governing permissions and limitations
# under the License.
""" """
Views for managing instances. Views for managing instances.
@@ -24,24 +25,42 @@ import logging
from django import http from django import http
from django import shortcuts from django import shortcuts
from django.views import generic
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.formtools.wizard.views import SessionWizardView
from horizon import exceptions from horizon import exceptions
from horizon import forms from horizon import forms
from horizon import tabs from horizon import tabs
from horizon import tables from horizon import tables
from horizon import workflows from horizon import workflows
from horizon.forms.views import ModalFormMixin
from openstack_dashboard import api from openstack_dashboard import api
from .tables import WinDCTable, WinServicesTable from .tables import WinDCTable, WinServicesTable
from .workflows import CreateWinService, CreateWinDC from .workflows import CreateWinService, CreateWinDC
from .forms import WizardFormServiceType, WizardFormConfiguration
import pdb
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class Wizard(ModalFormMixin, SessionWizardView, generic.FormView):
template_name = 'project/windc/services_tabs.html'
def done(self, form_list, **kwargs):
#do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/')
def get_form(self, step=None, data=None, files=None):
form = super(Wizard, self).get_form(step, data, files)
print step
print data
return form
class IndexView(tables.DataTableView): class IndexView(tables.DataTableView):
table_class = WinDCTable table_class = WinDCTable
template_name = 'project/windc/index.html' template_name = 'project/windc/index.html'
@@ -69,7 +88,7 @@ class WinServices(tables.DataTableView):
def get_data(self): def get_data(self):
try: try:
dc_id = self.kwargs['domain_controller_id'] dc_id = self.kwargs['data_center_id']
datacenter = api.windc.datacenters_get(self.request, dc_id) datacenter = api.windc.datacenters_get(self.request, dc_id)
self.dc_name = datacenter.name self.dc_name = datacenter.name
services = api.windc.services_list(self.request, datacenter) services = api.windc.services_list(self.request, datacenter)
@@ -91,6 +110,7 @@ class CreateWinDCView(workflows.WorkflowView):
initial['user_id'] = self.request.user.id initial['user_id'] = self.request.user.id
return initial return initial
class CreateWinServiceView(workflows.WorkflowView): class CreateWinServiceView(workflows.WorkflowView):
workflow_class = CreateWinService workflow_class = CreateWinService
template_name = "project/windc/create.html" template_name = "project/windc/create.html"

View File

@@ -84,11 +84,7 @@ class ConfigureWinDCAction(workflows.Action):
dc_name = forms.CharField(label=_("Domain Name"), dc_name = forms.CharField(label=_("Domain Name"),
required=False) required=False)
#dc_net_name = forms.CharField(label=_("Domain NetBIOS Name"), dc_count = forms.IntegerField(label=_("Instances Count"),
# required=False,
# help_text=_("A NetBIOS name of new domain."))
dc_count = forms.IntegerField(label=_("Domain Controllers Count"),
required=True, required=True,
min_value=1, min_value=1,
max_value=100, max_value=100,
@@ -108,7 +104,7 @@ class ConfigureWinDCAction(workflows.Action):
"Recovery Mode.")) "Recovery Mode."))
class Meta: class Meta:
name = _("Domain Controllers") name = _("Active Directory")
help_text_template = ("project/windc/_dc_help.html") help_text_template = ("project/windc/_dc_help.html")
@@ -192,7 +188,6 @@ class CreateWinService(workflows.Workflow):
return False return False
class CreateWinDC(workflows.Workflow): class CreateWinDC(workflows.Workflow):
slug = "create" slug = "create"
name = _("Create Windows Data Center") name = _("Create Windows Data Center")

View File

@@ -35,9 +35,9 @@ class DCManager(base.Manager):
body.update(extra) body.update(extra)
return self._create('/datacenters', body, 'datacenter') return self._create('/datacenters', body, 'datacenter')
def delete(self, datacenter): def delete(self, datacenter_id):
return self._delete("/datacenters/%s" % base.getid(datacenter)) return self._delete("/datacenters/%s" % datacenter_id)
def get(self, datacenter): def get(self, datacenter_id):
return self._get("/datacenters/%s" % base.getid(datacenter), return self._get("/datacenters/%s" % datacenter_id,
'datacenter') 'datacenter')

View File

@@ -37,10 +37,9 @@ class DCServiceManager(base.Manager):
return self._create("/datacenters/%s/services" % base.getid(datacenter), return self._create("/datacenters/%s/services" % base.getid(datacenter),
body, 'service') body, 'service')
def delete(self, datacenter, service): def delete(self, datacenter_id, service_id):
return self._delete("/datacenters/%s/services/%s" % \ return self._delete("/datacenters/%s/services/%s" % \
(base.getid(datacenter), (datacenter_id, service_id))
base.getid(service)))
def get(self, datacenter, service): def get(self, datacenter, service):
return self._get("/datacenters/%s/services/%s" % \ return self._get("/datacenters/%s/services/%s" % \

82
tests/deploy.sh Normal file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/expect -d
# The following directories should be created for this script:
# /opt/stack/devstack
# /opt/stack/keero
# the ssh key should be in directory /opt/stack/.ssh/
# the iso file with windows should be in directory /opt/stack/
set timeout 1200
send_user "\n\nStart to login to the test bed...\n\n"
spawn /usr/bin/ssh [lindex $argv 0]@[lindex $argv 1]
expect "password"
send -- "EVYiMCVZX9\n"
expect "*#*"
send -- "su - stack\n"
expect "*$*"
send -- "sudo killall python\n"
expect "*$*"
send -- "cd ~/devstack\n"
expect "*$*"
send -- "./unstack.sh\n"
expect "*$*"
send -- "./stack.sh\n"
expect "*Would you like to start it now?*"
send -- "y\n"
expect "*stack.sh completed*"
send -- "source openrc admin admin\n"
expect "*$*"
send -- "cd ~\n"
expect "*$*"
send -- "nova keypair-add keero-linux-keys > heat_key.priv\n"
expect "*$*"
send -- "glance image-create --name 'ws-2012-full-agent' --is-public true --container-format ovf --disk-format qcow2 < ws-2012-full-agent.qcow2\n"
expect "*$*"
send -- "cd ~/keero\n"
expect "*$*"
send -- "git pull\n"
expect "/.ssh/id_rsa"
send -- "swordfish\n"
expect "*$*"
send -- "cp -Rf ~/keero/dashboard/windc /opt/stack/horizon/openstack_dashboard/dashboards/project\n"
expect "*$*"
send -- "cp -f ~/keero/dashboard/api/windc.py /opt/stack/horizon/openstack_dashboard/api/\n"
expect "*$*"
send -- "cp -Rf ~/keero/dashboard/windcclient /opt/stack/horizon/\n"
expect "*$*"
send -- "cd ~/keero/windc\n"
expect "*$*"
send -- "rm -rf windc.sqlite\n"
expect "*$*"
send -- "./tools/with_venv.sh ./bin/windc-api --config-file=./etc/windc-api-paste.ini --dbsync\n"
expect "*$*"
send -- "logout\n"
expect "*#*"
send -- "rabbitmq-plugins enable rabbitmq_management\n"
expect "*#*"
send -- "service rabbitmq-server restart\n"
expect "*#*"
send -- "rabbitmqctl add_user keero keero\n"
expect "*#*"
send -- "rabbitmqctl set_user_tags keero administrator\n"
expect "*#*"
send -- "su - stack\n"
expect "*$*"
send -- "cd /opt/stack/devstack\n"
expect "*$*"
send -- "source openrc admin admin\n"
expect "*$*"
send -- "cd /opt/stack/keero/windc\n"
expect "*$*"
send -- "sudo ./tools/with_venv.sh ./bin/windc-api --config-file=./etc/windc-api-paste.ini > /opt/stack/tests_windc_daemon.log &\n"
expect "*$*"

4
tests/selenium/conf.ini Normal file
View File

@@ -0,0 +1,4 @@
[server]
address=http://172.18.124.101
user=admin
password=AkvareL707

View File

@@ -0,0 +1,55 @@
import re
from login_page import LoginPage
class DataCentersPage():
page = None
def __init__(self):
start_page = LoginPage()
self.page = start_page.login()
self.page.find_element_by_link_text('Project').click()
self.page.find_element_by_link_text('Windows Data Centers').click()
def create_data_center(self, name):
button_text = 'Create Windows Data Center'
self.page.find_element_by_link_text(button_text).click()
name_field = self.page.find_element_by_id('id_name')
xpath = "//input[@value='Create']"
button = self.page.find_element_by_xpath(xpath)
name_field.clear()
name_field.send_keys(name)
button.click()
return self.page
def find_data_center(self, name):
return self.page.find_element_by_link_text(name)
def delete_data_center(self, name):
datacenter = self.find_data_center(name)
link = datacenter.get_attribute('href')
datacenter_id = re.search('windc/(\S+)', link).group(0)[6:-1]
xpath = ".//*[@id='windc__row__%s']/td[3]/div/a[2]" % datacenter_id
more_button = self.page.find_element_by_xpath(xpath)
more_button.click()
delete_button_id = "windc__row_%s__action_delete" % datacenter_id
delete_button = self.page.find_element_by_id(delete_button_id)
delete_button.click()
self.page.find_element_by_link_text("Delete Data Center").click()
return self.page
def select_data_center(self, name):
datacenter = self.page.find_data_center(name)
datacenter.click()
return self.page

View File

@@ -0,0 +1,30 @@
import ConfigParser
from selenium import webdriver
class LoginPage():
def login(self):
config = ConfigParser.RawConfigParser()
config.read('conf.ini')
url = config.get('server', 'address')
user = config.get('server', 'user')
password = config.get('server', 'password')
page = webdriver.Firefox()
page.set_page_load_timeout(30)
page.implicitly_wait(30)
page.get(url)
name = page.find_element_by_name('username')
pwd = page.find_element_by_name('password')
xpath = "//button[@type='submit']"
button = page.find_element_by_xpath(xpath)
name.clear()
name.send_keys(user)
pwd.clear()
pwd.send_keys(password)
button.click()
return page

View File

@@ -0,0 +1,55 @@
import ConfigParser
from selenium import webdriver
class ServicesPage():
page = None
def __init__(self, page):
self.page = page
def create_service(self, service_type, parameters):
button_id = 'services__action_CreateService'
button = self.page.find_element_by_id(button_id)
button.click()
self.select_type_of_service(service_type)
for parameter in parameters:
field = self.page.find_element_by_name(parameter.key)
field.clear()
field.send_keys(parameter.value)
xpath = "//input[@value='Deploy']"
deploy_button = self.page.find_element_by_xpath(xpath)
deploy_button.click()
return page
def select_type_of_service(self, service_type):
tab = find_element_by_link_text(service_type)
tab.click()
return self.page
def find_service(self, name):
return self.page.find_element_by_link_text(name)
def delete_service(self, name):
service = self.find_data_center(name)
link = service.get_attribute('href')
service_id = re.search('windc/(\S+)', link).group(0)[6:-1]
xpath = ".//*[@id='services__row__%s']/td[5]/div/a[2]" % service_id
more_button = self.page.find_element_by_xpath(xpath)
more_button.click()
delete_button_id = "services__row_%s__action_delete" % datacenter_id
delete_button = self.page.find_element_by_id(delete_button_id)
delete_button.click()
self.page.find_element_by_link_text("Delete Service").click()
return self.page

35
tests/selenium/test.py Normal file
View File

@@ -0,0 +1,35 @@
import untitests
from datacenters_page import DataCentersPage
class SanityTests():
def setUp(self):
self.page = DataCentersPage()
def tearDown(self):
self.page.close()
def test_01_create_data_center(self):
self.page.create_data_center('dc1')
assert self.page.find_data_center('dc1') is not None
def test_02_delete_data_center(self):
page.delete_data_center('dc1')
assert self.page.find_data_center('dc1') is None
def test_03_create_data_centers(self):
for i in range(1, 20):
name = 'datacenter' + str(i)
self.page.create_data_center(name)
assert self.page.find_data_center(name) is not None
def test_04_delete_data_centers(self):
page.delete_data_center('datacenter1')
page.delete_data_center('datacenter20')
assert self.page.find_data_center('datacenter1') is None
assert self.page.find_data_center('datacenter20') is None
for i in range(2, 19):
name = 'datacenter' + str(i)
assert self.page.find_data_center(name) is not None