From 3b0f8570def8a70245e454f60043aa86e375a8a8 Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Wed, 13 Feb 2013 20:49:28 +0400 Subject: [PATCH 01/21] Added initial project for horizon dashboard --- dashboard/ReadMe.txt | 30 ++ dashboard/windc/__init__.py | 0 dashboard/windc/forms.py | 52 ++ dashboard/windc/panel.py | 29 ++ dashboard/windc/tables.py | 278 +++++++++++ dashboard/windc/tabs.py | 85 ++++ dashboard/windc/templates/windc/create.html | 11 + dashboard/windc/templates/windc/index.html | 11 + dashboard/windc/urls.py | 31 ++ dashboard/windc/views.py | 95 ++++ dashboard/windc/workflows.py | 514 ++++++++++++++++++++ 11 files changed, 1136 insertions(+) create mode 100644 dashboard/ReadMe.txt create mode 100644 dashboard/windc/__init__.py create mode 100644 dashboard/windc/forms.py create mode 100644 dashboard/windc/panel.py create mode 100644 dashboard/windc/tables.py create mode 100644 dashboard/windc/tabs.py create mode 100644 dashboard/windc/templates/windc/create.html create mode 100644 dashboard/windc/templates/windc/index.html create mode 100644 dashboard/windc/urls.py create mode 100644 dashboard/windc/views.py create mode 100644 dashboard/windc/workflows.py diff --git a/dashboard/ReadMe.txt b/dashboard/ReadMe.txt new file mode 100644 index 0000000..39e951e --- /dev/null +++ b/dashboard/ReadMe.txt @@ -0,0 +1,30 @@ +# TO DO: +# 1. Fix issue with Create button +# 2. Create simple form for Windows Data Center deploy +# 3. Remove extra code +# + +This file is described how to install new tab on horizon dashboard. +We should do the following: + 1. Copy directory 'windc' to directory '/opt/stack/horizon/openstack_dashboard/dashboards/project' + 2. Edit file '/opt/stack/horizon/openstack_dashboard/dashboards/project/dashboard.py' + Add line with windc project: + + ... +class BasePanels(horizon.PanelGroup): + slug = "compute" + name = _("Manage Compute") + panels = ('overview', + 'instances', + 'volumes', + 'images_and_snapshots', + 'access_and_security', + 'networks', + 'routers', + 'windc') + + ... + + 3. Run the test Django server: + cd /opt/stack/horizon + python manage.py runserver 67.207.197.36:8080 \ No newline at end of file diff --git a/dashboard/windc/__init__.py b/dashboard/windc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/windc/forms.py b/dashboard/windc/forms.py new file mode 100644 index 0000000..518a9a0 --- /dev/null +++ b/dashboard/windc/forms.py @@ -0,0 +1,52 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from openstack_dashboard import api + +from horizon import exceptions +from horizon import forms +from horizon import messages + + +LOG = logging.getLogger(__name__) + + +class UpdateInstance(forms.SelfHandlingForm): + tenant_id = forms.CharField(widget=forms.HiddenInput) + instance = forms.CharField(widget=forms.HiddenInput) + name = forms.CharField(required=True) + + def handle(self, request, data): + try: + server = api.nova.server_update(request, data['instance'], + data['name']) + messages.success(request, + _('Instance "%s" updated.') % data['name']) + return server + except: + redirect = reverse("horizon:project:instances:index") + exceptions.handle(request, + _('Unable to update instance.'), + redirect=redirect) diff --git a/dashboard/windc/panel.py b/dashboard/windc/panel.py new file mode 100644 index 0000000..cc464af --- /dev/null +++ b/dashboard/windc/panel.py @@ -0,0 +1,29 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.dashboards.project import dashboard + + +class WinDC(horizon.Panel): + name = _("WinDC") + slug = 'windc' + + +dashboard.Project.register(WinDC) diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py new file mode 100644 index 0000000..d2aad13 --- /dev/null +++ b/dashboard/windc/tables.py @@ -0,0 +1,278 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django import shortcuts +from django import template +from django.core import urlresolvers +from django.template.defaultfilters import title +from django.utils.http import urlencode +from django.utils.translation import string_concat, ugettext_lazy as _ + +from horizon.conf import HORIZON_CONFIG +from horizon import exceptions +from horizon import messages +from horizon import tables +from horizon.templatetags import sizeformat +from horizon.utils.filters import replace_underscores + +from openstack_dashboard import api +from openstack_dashboard.dashboards.project.access_and_security \ + .floating_ips.workflows import IPAssociationWorkflow +from .tabs import InstanceDetailTabs, LogTab, ConsoleTab + + +LOG = logging.getLogger(__name__) + +ACTIVE_STATES = ("ACTIVE",) + +POWER_STATES = { + 0: "NO STATE", + 1: "RUNNING", + 2: "BLOCKED", + 3: "PAUSED", + 4: "SHUTDOWN", + 5: "SHUTOFF", + 6: "CRASHED", + 7: "SUSPENDED", + 8: "FAILED", + 9: "BUILDING", +} + +PAUSE = 0 +UNPAUSE = 1 +SUSPEND = 0 +RESUME = 1 + + +def is_deleting(instance): + task_state = getattr(instance, "OS-EXT-STS:task_state", None) + if not task_state: + return False + return task_state.lower() == "deleting" + + +class RebootWinDC(tables.BatchAction): + name = "reboot" + action_present = _("Reboot") + action_past = _("Rebooted") + data_type_singular = _("Instance") + data_type_plural = _("Instances") + classes = ('btn-danger', 'btn-reboot') + + def allowed(self, request, instance=None): + return ((instance.status in ACTIVE_STATES + or instance.status == 'SHUTOFF') + and not is_deleting(instance)) + + def action(self, request, obj_id): + api.nova.server_reboot(request, obj_id) + + +class CreateWinDC(tables.LinkAction): + name = "CreateWinDC" + verbose_name = _("Create WinDC") + url = "horizon:project:windc:create" + classes = ("btn-launch", "ajax-modal") + + def allowed(self, request, datum): + try: + limits = api.nova.tenant_absolute_limits(request, reserved=True) + + instances_available = limits['maxTotalInstances'] \ + - limits['totalInstancesUsed'] + cores_available = limits['maxTotalCores'] \ + - limits['totalCoresUsed'] + ram_available = limits['maxTotalRAMSize'] - limits['totalRAMUsed'] + + if instances_available <= 0 or cores_available <= 0 \ + or ram_available <= 0: + if "disabled" not in self.classes: + self.classes = [c for c in self.classes] + ['disabled'] + self.verbose_name = string_concat(self.verbose_name, ' ', + _("(Quota exceeded)")) + else: + self.verbose_name = _("Create WinDC") + classes = [c for c in self.classes if c != "disabled"] + self.classes = classes + except: + LOG.exception("Failed to retrieve quota information") + # If we can't get the quota information, leave it to the + # API to check when launching + + return True # The action should always be displayed + + +class DeleteWinDC(tables.BatchAction): + name = "DeleteWinDC" + action_present = _("DeleteWinDC") + action_past = _("Scheduled termination of") + data_type_singular = _("Instance") + data_type_plural = _("Instances") + classes = ('btn-danger', 'btn-terminate') + + def allowed(self, request, instance=None): + if instance: + # FIXME(gabriel): This is true in Essex, but in FOLSOM an instance + # can be terminated in any state. We should improve this error + # handling when LP bug 1037241 is implemented. + return instance.status not in ("PAUSED", "SUSPENDED") + return True + + def action(self, request, obj_id): + api.nova.server_delete(request, obj_id) + + +class EditWinDC(tables.LinkAction): + name = "edit" + verbose_name = _("Edit Instance") + url = "horizon:project:instances:update" + classes = ("ajax-modal", "btn-edit") + + def allowed(self, request, instance): + return not is_deleting(instance) + + +class ConsoleLink(tables.LinkAction): + name = "console" + verbose_name = _("Console") + url = "horizon:project:instances:detail" + classes = ("btn-console",) + + def allowed(self, request, instance=None): + return instance.status in ACTIVE_STATES and not is_deleting(instance) + + def get_link_url(self, datum): + base_url = super(ConsoleLink, self).get_link_url(datum) + tab_query_string = ConsoleTab(InstanceDetailTabs).get_query_string() + return "?".join([base_url, tab_query_string]) + + +class LogLink(tables.LinkAction): + name = "log" + verbose_name = _("View Log") + url = "horizon:project:instances:detail" + classes = ("btn-log",) + + def allowed(self, request, instance=None): + return instance.status in ACTIVE_STATES and not is_deleting(instance) + + def get_link_url(self, datum): + base_url = super(LogLink, self).get_link_url(datum) + tab_query_string = LogTab(InstanceDetailTabs).get_query_string() + return "?".join([base_url, tab_query_string]) + + +class UpdateRow(tables.Row): + ajax = True + + def get_data(self, request, instance_id): + instance = api.nova.server_get(request, instance_id) + instance.full_flavor = api.nova.flavor_get(request, + instance.flavor["id"]) + return instance + + +def get_ips(instance): + template_name = 'project/instances/_instance_ips.html' + context = {"instance": instance} + return template.loader.render_to_string(template_name, context) + + +def get_size(instance): + if hasattr(instance, "full_flavor"): + size_string = _("%(name)s | %(RAM)s RAM | %(VCPU)s VCPU " + "| %(disk)s Disk") + vals = {'name': instance.full_flavor.name, + 'RAM': sizeformat.mbformat(instance.full_flavor.ram), + 'VCPU': instance.full_flavor.vcpus, + 'disk': sizeformat.diskgbformat(instance.full_flavor.disk)} + return size_string % vals + return _("Not available") + + +def get_keyname(instance): + if hasattr(instance, "key_name"): + keyname = instance.key_name + return keyname + return _("Not available") + + +def get_power_state(instance): + return POWER_STATES.get(getattr(instance, "OS-EXT-STS:power_state", 0), '') + + +STATUS_DISPLAY_CHOICES = ( + ("resize", "Resize/Migrate"), + ("verify_resize", "Confirm or Revert Resize/Migrate"), + ("revert_resize", "Revert Resize/Migrate"), +) + + +TASK_DISPLAY_CHOICES = ( + ("image_snapshot", "Snapshotting"), + ("resize_prep", "Preparing Resize or Migrate"), + ("resize_migrating", "Resizing or Migrating"), + ("resize_migrated", "Resized or Migrated"), + ("resize_finish", "Finishing Resize or Migrate"), + ("resize_confirming", "Confirming Resize or Nigrate"), + ("resize_reverting", "Reverting Resize or Migrate"), + ("unpausing", "Resuming"), +) + + +class WinDCTable(tables.DataTable): + TASK_STATUS_CHOICES = ( + (None, True), + ("none", True) + ) + STATUS_CHOICES = ( + ("active", True), + ("shutoff", True), + ("error", False), + ) + name = tables.Column("name", + link=("horizon:project:instances:detail"), + verbose_name=_("WinDC Instance Name")) + ip = tables.Column(get_ips, verbose_name=_("IP Address")) + size = tables.Column(get_size, + verbose_name=_("Type"), + attrs={'data-type': 'type'}) + keypair = tables.Column(get_keyname, verbose_name=_("Keypair")) + status = tables.Column("status", + filters=(title, replace_underscores), + verbose_name=_("Status"), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + task = tables.Column("OS-EXT-STS:task_state", + verbose_name=_("Task"), + filters=(title, replace_underscores), + status=True, + status_choices=TASK_STATUS_CHOICES, + display_choices=TASK_DISPLAY_CHOICES) + state = tables.Column(get_power_state, + filters=(title, replace_underscores), + verbose_name=_("Power State")) + + class Meta: + name = "windc" + verbose_name = _("WinDC") + status_columns = ["status", "task"] + row_class = UpdateRow + table_actions = (CreateWinDC, DeleteWinDC) + row_actions = (EditWinDC, ConsoleLink, LogLink, RebootWinDC) diff --git a/dashboard/windc/tabs.py b/dashboard/windc/tabs.py new file mode 100644 index 0000000..029c877 --- /dev/null +++ b/dashboard/windc/tabs.py @@ -0,0 +1,85 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tabs + +from openstack_dashboard import api + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = ("project/instances/" + "_detail_overview.html") + + def get_context_data(self, request): + return {"instance": self.tab_group.kwargs['instance']} + + +class LogTab(tabs.Tab): + name = _("Log") + slug = "log" + template_name = "project/instances/_detail_log.html" + preload = False + + def get_context_data(self, request): + instance = self.tab_group.kwargs['instance'] + try: + data = api.nova.server_console_output(request, + instance.id, + tail_length=35) + except: + data = _('Unable to get log for instance "%s".') % instance.id + exceptions.handle(request, ignore=True) + return {"instance": instance, + "console_log": data} + + +class ConsoleTab(tabs.Tab): + name = _("Console") + slug = "console" + template_name = "project/instances/_detail_console.html" + preload = False + + def get_context_data(self, request): + instance = self.tab_group.kwargs['instance'] + # Currently prefer VNC over SPICE, since noVNC has had much more + # testing than spice-html5 + try: + console = api.nova.server_vnc_console(request, instance.id) + console_url = "%s&title=%s(%s)" % ( + console.url, + getattr(instance, "name", ""), + instance.id) + except: + try: + console = api.nova.server_spice_console(request, instance.id) + console_url = "%s&title=%s(%s)" % ( + console.url, + getattr(instance, "name", ""), + instance.id) + except: + console_url = None + return {'console_url': console_url, 'instance_id': instance.id} + + +class InstanceDetailTabs(tabs.TabGroup): + slug = "instance_details" + tabs = (OverviewTab, LogTab, ConsoleTab) + sticky = True diff --git a/dashboard/windc/templates/windc/create.html b/dashboard/windc/templates/windc/create.html new file mode 100644 index 0000000..cc5e244 --- /dev/null +++ b/dashboard/windc/templates/windc/create.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create WinDC" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("CreateWinDC") %} +{% endblock page_header %} + +{% block main %} + {% include 'horizon/common/_workflow.html' %} +{% endblock %} diff --git a/dashboard/windc/templates/windc/index.html b/dashboard/windc/templates/windc/index.html new file mode 100644 index 0000000..13d24d8 --- /dev/null +++ b/dashboard/windc/templates/windc/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "WinDC" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("WinDC") %} +{% endblock page_header %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/dashboard/windc/urls.py b/dashboard/windc/urls.py new file mode 100644 index 0000000..ec6314c --- /dev/null +++ b/dashboard/windc/urls.py @@ -0,0 +1,31 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls.defaults import patterns, url + +from .views import IndexView, CreateWinDCView + + +VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views' + +urlpatterns = patterns(VIEW_MOD, + url(r'^$', IndexView.as_view(), name='index'), + url(r'^create/$', CreateWinDCView.as_view(), name='CreateWinDC') +) diff --git a/dashboard/windc/views.py b/dashboard/windc/views.py new file mode 100644 index 0000000..a5fd2f6 --- /dev/null +++ b/dashboard/windc/views.py @@ -0,0 +1,95 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Views for managing instances. +""" +import logging + +from django import http +from django import shortcuts +from django.core.urlresolvers import reverse, reverse_lazy +from django.utils.datastructures import SortedDict +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import tabs +from horizon import tables +from horizon import workflows + +from openstack_dashboard import api +from .forms import UpdateInstance +from .tabs import InstanceDetailTabs +from .tables import WinDCTable +from .workflows import CreateWinDC + + +LOG = logging.getLogger(__name__) + + +class IndexView(tables.DataTableView): + table_class = WinDCTable + template_name = 'project/windc/index.html' + + def get_data(self): + # Gather our instances + try: + instances = api.nova.server_list(self.request) + except: + instances = [] + exceptions.handle(self.request, + _('Unable to retrieve instances.')) + # Gather our flavors and correlate our instances to them + if instances: + try: + flavors = api.nova.flavor_list(self.request) + except: + flavors = [] + exceptions.handle(self.request, ignore=True) + + full_flavors = SortedDict([(str(flavor.id), flavor) + for flavor in flavors]) + # Loop through instances to get flavor info. + for instance in instances: + try: + flavor_id = instance.flavor["id"] + if flavor_id in full_flavors: + instance.full_flavor = full_flavors[flavor_id] + else: + # If the flavor_id is not in full_flavors list, + # get it via nova api. + instance.full_flavor = api.nova.flavor_get( + self.request, flavor_id) + except: + msg = _('Unable to retrieve instance size information.') + exceptions.handle(self.request, msg) + return instances + + +class CreateWinDCView(workflows.WorkflowView): + workflow_class = CreateWinDC + template_name = "project/windc/create.html" + + def get_initial(self): + initial = super(CreateWinDCView, self).get_initial() + initial['project_id'] = self.request.user.tenant_id + initial['user_id'] = self.request.user.id + return initial diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py new file mode 100644 index 0000000..a282eec --- /dev/null +++ b/dashboard/windc/workflows.py @@ -0,0 +1,514 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import logging + +from django.utils.text import normalize_newlines +from django.utils.translation import ugettext as _ + +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from openstack_dashboard import api +from openstack_dashboard.api import cinder +from openstack_dashboard.api import glance +from openstack_dashboard.usage import quotas + + +LOG = logging.getLogger(__name__) + + +class SelectProjectUserAction(workflows.Action): + project_id = forms.ChoiceField(label=_("Project")) + user_id = forms.ChoiceField(label=_("User")) + + def __init__(self, request, *args, **kwargs): + super(SelectProjectUserAction, self).__init__(request, *args, **kwargs) + # Set our project choices + projects = [(tenant.id, tenant.name) + for tenant in request.user.authorized_tenants] + self.fields['project_id'].choices = projects + + # Set our user options + users = [(request.user.id, request.user.username)] + self.fields['user_id'].choices = users + + class Meta: + name = _("Project & User") + # Unusable permission so this is always hidden. However, we + # keep this step in the workflow for validation/verification purposes. + permissions = ("!",) + + +class SelectProjectUser(workflows.Step): + action_class = SelectProjectUserAction + contributes = ("project_id", "user_id") + + +class VolumeOptionsAction(workflows.Action): + VOLUME_CHOICES = ( + ('', _("Don't boot from a volume.")), + ("volume_id", _("Boot from volume.")), + ("volume_snapshot_id", _("Boot from volume snapshot " + "(creates a new volume).")), + ) + # Boot from volume options + volume_type = forms.ChoiceField(label=_("Volume Options"), + choices=VOLUME_CHOICES, + required=False) + volume_id = forms.ChoiceField(label=_("Volume"), required=False) + volume_snapshot_id = forms.ChoiceField(label=_("Volume Snapshot"), + required=False) + device_name = forms.CharField(label=_("Device Name"), + required=False, + initial="vda", + help_text=_("Volume mount point (e.g. 'vda' " + "mounts at '/dev/vda').")) + delete_on_terminate = forms.BooleanField(label=_("Delete on Terminate"), + initial=False, + required=False, + help_text=_("Delete volume on " + "instance terminate")) + + class Meta: + name = _("Volume Options") + permissions = ('openstack.services.volume',) + help_text_template = ("project/instances/" + "_launch_volumes_help.html") + + def clean(self): + cleaned_data = super(VolumeOptionsAction, self).clean() + volume_opt = cleaned_data.get('volume_type', None) + + if volume_opt and not cleaned_data[volume_opt]: + raise forms.ValidationError(_('Please choose a volume, or select ' + '%s.') % self.VOLUME_CHOICES[0][1]) + return cleaned_data + + def _get_volume_display_name(self, volume): + if hasattr(volume, "volume_id"): + vol_type = "snap" + visible_label = _("Snapshot") + else: + vol_type = "vol" + visible_label = _("Volume") + return (("%s:%s" % (volume.id, vol_type)), + ("%s - %s GB (%s)" % (volume.display_name, + volume.size, + visible_label))) + + def populate_volume_id_choices(self, request, context): + volume_options = [("", _("Select Volume"))] + try: + volumes = [v for v in cinder.volume_list(self.request) + if v.status == api.cinder.VOLUME_STATE_AVAILABLE] + volume_options.extend([self._get_volume_display_name(vol) + for vol in volumes]) + except: + exceptions.handle(self.request, + _('Unable to retrieve list of volumes.')) + return volume_options + + def populate_volume_snapshot_id_choices(self, request, context): + volume_options = [("", _("Select Volume Snapshot"))] + try: + snapshots = cinder.volume_snapshot_list(self.request) + snapshots = [s for s in snapshots + if s.status == api.cinder.VOLUME_STATE_AVAILABLE] + volume_options.extend([self._get_volume_display_name(snap) + for snap in snapshots]) + except: + exceptions.handle(self.request, + _('Unable to retrieve list of volume ' + 'snapshots.')) + + return volume_options + + +class VolumeOptions(workflows.Step): + action_class = VolumeOptionsAction + depends_on = ("project_id", "user_id") + contributes = ("volume_type", + "volume_id", + "device_name", # Can be None for an image. + "delete_on_terminate") + + def contribute(self, data, context): + context = super(VolumeOptions, self).contribute(data, context) + # Translate form input to context for volume values. + if "volume_type" in data and data["volume_type"]: + context['volume_id'] = data.get(data['volume_type'], None) + + if not context.get("volume_type", ""): + context['volume_type'] = self.action.VOLUME_CHOICES[0][0] + context['volume_id'] = None + context['device_name'] = None + context['delete_on_terminate'] = None + return context + + +class SetInstanceDetailsAction(workflows.Action): + SOURCE_TYPE_CHOICES = ( + ("image_id", _("Image")), + ("instance_snapshot_id", _("Snapshot")), + ) + source_type = forms.ChoiceField(label=_("Instance Source"), + choices=SOURCE_TYPE_CHOICES) + image_id = forms.ChoiceField(label=_("Image"), required=False) + instance_snapshot_id = forms.ChoiceField(label=_("Instance Snapshot"), + required=False) + name = forms.CharField(max_length=80, label=_("Instance Name")) + flavor = forms.ChoiceField(label=_("Flavor"), + help_text=_("Size of image to launch.")) + count = forms.IntegerField(label=_("Instance Count"), + min_value=1, + initial=1, + help_text=_("Number of instances to launch.")) + + class Meta: + name = _("Details") + help_text_template = ("project/instances/" + "_launch_details_help.html") + + def clean(self): + cleaned_data = super(SetInstanceDetailsAction, self).clean() + + # Validate our instance source. + source = cleaned_data['source_type'] + # There should always be at least one image_id choice, telling the user + # that there are "No Images Available" so we check for 2 here... + if source == 'image_id' and not \ + filter(lambda x: x[0] != '', self.fields['image_id'].choices): + raise forms.ValidationError(_("There are no image sources " + "available; you must first create " + "an image before attempting to " + "launch an instance.")) + if not cleaned_data[source]: + raise forms.ValidationError(_("Please select an option for the " + "instance source.")) + + # Prevent launching multiple instances with the same volume. + # TODO(gabriel): is it safe to launch multiple instances with + # a snapshot since it should be cloned to new volumes? + count = cleaned_data.get('count', 1) + volume_type = self.data.get('volume_type', None) + if volume_type and count > 1: + msg = _('Launching multiple instances is only supported for ' + 'images and instance snapshots.') + raise forms.ValidationError(msg) + + return cleaned_data + + def _get_available_images(self, request, context): + project_id = context.get('project_id', None) + if not hasattr(self, "_public_images"): + public = {"is_public": True, + "status": "active"} + try: + public_images, _more = glance.image_list_detailed( + request, filters=public) + except: + public_images = [] + exceptions.handle(request, + _("Unable to retrieve public images.")) + self._public_images = public_images + + # Preempt if we don't have a project_id yet. + if project_id is None: + setattr(self, "_images_for_%s" % project_id, []) + + if not hasattr(self, "_images_for_%s" % project_id): + owner = {"property-owner_id": project_id, + "status": "active"} + try: + owned_images, _more = glance.image_list_detailed( + request, filters=owner) + except: + exceptions.handle(request, + _("Unable to retrieve images for " + "the current project.")) + setattr(self, "_images_for_%s" % project_id, owned_images) + + owned_images = getattr(self, "_images_for_%s" % project_id) + images = owned_images + self._public_images + + # Remove duplicate images + image_ids = [] + final_images = [] + for image in images: + if image.id not in image_ids: + image_ids.append(image.id) + final_images.append(image) + return [image for image in final_images + if image.container_format not in ('aki', 'ari')] + + def populate_image_id_choices(self, request, context): + images = self._get_available_images(request, context) + choices = [(image.id, image.name) + for image in images + if image.properties.get("image_type", '') != "snapshot"] + if choices: + choices.insert(0, ("", _("Select Image"))) + else: + choices.insert(0, ("", _("No images available."))) + return choices + + def populate_instance_snapshot_id_choices(self, request, context): + images = self._get_available_images(request, context) + choices = [(image.id, image.name) + for image in images + if image.properties.get("image_type", '') == "snapshot"] + if choices: + choices.insert(0, ("", _("Select Instance Snapshot"))) + else: + choices.insert(0, ("", _("No snapshots available."))) + return choices + + def populate_flavor_choices(self, request, context): + try: + flavors = api.nova.flavor_list(request) + flavor_list = [(flavor.id, "%s" % flavor.name) + for flavor in flavors] + except: + flavor_list = [] + exceptions.handle(request, + _('Unable to retrieve instance flavors.')) + return sorted(flavor_list) + + def get_help_text(self): + extra = {} + try: + extra['usages'] = quotas.tenant_quota_usages(self.request) + extra['usages_json'] = json.dumps(extra['usages']) + flavors = json.dumps([f._info for f in + api.nova.flavor_list(self.request)]) + extra['flavors'] = flavors + except: + exceptions.handle(self.request, + _("Unable to retrieve quota information.")) + return super(SetInstanceDetailsAction, self).get_help_text(extra) + + +class SetInstanceDetails(workflows.Step): + action_class = SetInstanceDetailsAction + contributes = ("source_type", "source_id", "name", "count", "flavor") + + def prepare_action_context(self, request, context): + if 'source_type' in context and 'source_id' in context: + context[context['source_type']] = context['source_id'] + return context + + def contribute(self, data, context): + context = super(SetInstanceDetails, self).contribute(data, context) + # Allow setting the source dynamically. + if ("source_type" in context and "source_id" in context + and context["source_type"] not in context): + context[context["source_type"]] = context["source_id"] + + # Translate form input to context for source values. + if "source_type" in data: + context["source_id"] = data.get(data['source_type'], None) + + return context + + +KEYPAIR_IMPORT_URL = "horizon:project:access_and_security:keypairs:import" + + +class SetAccessControlsAction(workflows.Action): + keypair = forms.DynamicChoiceField(label=_("Keypair"), + required=False, + help_text=_("Which keypair to use for " + "authentication."), + add_item_link=KEYPAIR_IMPORT_URL) + groups = forms.MultipleChoiceField(label=_("Security Groups"), + required=True, + initial=["default"], + widget=forms.CheckboxSelectMultiple(), + help_text=_("Launch instance in these " + "security groups.")) + + class Meta: + name = _("Access & Security") + help_text = _("Control access to your instance via keypairs, " + "security groups, and other mechanisms.") + + def populate_keypair_choices(self, request, context): + try: + keypairs = api.nova.keypair_list(request) + keypair_list = [(kp.name, kp.name) for kp in keypairs] + except: + keypair_list = [] + exceptions.handle(request, + _('Unable to retrieve keypairs.')) + if keypair_list: + keypair_list.insert(0, ("", _("Select a keypair"))) + else: + keypair_list = (("", _("No keypairs available.")),) + return keypair_list + + def populate_groups_choices(self, request, context): + try: + groups = api.nova.security_group_list(request) + security_group_list = [(sg.name, sg.name) for sg in groups] + except: + exceptions.handle(request, + _('Unable to retrieve list of security groups')) + security_group_list = [] + return security_group_list + + +class SetAccessControls(workflows.Step): + action_class = SetAccessControlsAction + depends_on = ("project_id", "user_id") + contributes = ("keypair_id", "security_group_ids") + + def contribute(self, data, context): + if data: + post = self.workflow.request.POST + context['security_group_ids'] = post.getlist("groups") + context['keypair_id'] = data.get("keypair", "") + return context + + +class CustomizeAction(workflows.Action): + customization_script = forms.CharField(widget=forms.Textarea, + label=_("Customization Script"), + required=False, + help_text=_("A script or set of " + "commands to be " + "executed after the " + "instance has been " + "built (max 16kb).")) + + class Meta: + name = _("Post-Creation") + help_text_template = ("project/instances/" + "_launch_customize_help.html") + + +class PostCreationStep(workflows.Step): + action_class = CustomizeAction + contributes = ("customization_script",) + + +class SetNetworkAction(workflows.Action): + network = forms.MultipleChoiceField(label=_("Networks"), + required=True, + widget=forms.CheckboxSelectMultiple(), + help_text=_("Launch instance with" + "these networks")) + + class Meta: + name = _("Networking") + permissions = ('openstack.services.network',) + help_text = _("Select networks for your instance.") + + def populate_network_choices(self, request, context): + try: + tenant_id = self.request.user.tenant_id + networks = api.quantum.network_list_for_tenant(request, tenant_id) + for n in networks: + n.set_id_as_name_if_empty() + network_list = [(network.id, network.name) for network in networks] + except: + network_list = [] + exceptions.handle(request, + _('Unable to retrieve networks.')) + return network_list + + +class SetNetwork(workflows.Step): + action_class = SetNetworkAction + contributes = ("network_id",) + + def contribute(self, data, context): + if data: + networks = self.workflow.request.POST.getlist("network") + # If no networks are explicitly specified, network list + # contains an empty string, so remove it. + networks = [n for n in networks if n != ''] + if networks: + context['network_id'] = networks + return context + + +class CreateWinDC(workflows.Workflow): + slug = "create_windc" + name = _("Create WinDC Instance") + finalize_button_name = _("Deploy") + success_message = _('Deployed %(count)s named "%(name)s".') + failure_message = _('Unable to deploy %(count)s named "%(name)s".') + success_url = "horizon:project:windc:index" + default_steps = (SelectProjectUser, + SetInstanceDetails, + SetAccessControls, + SetNetwork, + VolumeOptions, + PostCreationStep) + + def format_status_message(self, message): + name = self.context.get('name', 'unknown instance') + count = self.context.get('count', 1) + if int(count) > 1: + return message % {"count": _("%s instances") % count, + "name": name} + else: + return message % {"count": _("instance"), "name": name} + + def handle(self, request, context): + custom_script = context.get('customization_script', '') + + # Determine volume mapping options + if context.get('volume_type', None): + if(context['delete_on_terminate']): + del_on_terminate = 1 + else: + del_on_terminate = 0 + mapping_opts = ("%s::%s" + % (context['volume_id'], del_on_terminate)) + dev_mapping = {context['device_name']: mapping_opts} + else: + dev_mapping = None + + netids = context.get('network_id', None) + if netids: + nics = [{"net-id": netid, "v4-fixed-ip": ""} + for netid in netids] + else: + nics = None + + try: + api.nova.server_create(request, + context['name'], + context['source_id'], + context['flavor'], + context['keypair_id'], + normalize_newlines(custom_script), + context['security_group_ids'], + dev_mapping, + nics=nics, + instance_count=int(context['count'])) + return True + except: + exceptions.handle(request) + return False From f32b95e7dce5a76dbbc02fe81ad4403bbd9c5a65 Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Thu, 14 Feb 2013 16:48:52 +0400 Subject: [PATCH 02/21] Fixed small problems with links and titles on pages. --- dashboard/windc/tables.py | 19 +++---------------- dashboard/windc/templates/windc/create.html | 4 ++-- dashboard/windc/templates/windc/index.html | 4 ++-- dashboard/windc/urls.py | 2 +- dashboard/windc/views.py | 2 -- dashboard/windc/workflows.py | 2 +- 6 files changed, 9 insertions(+), 24 deletions(-) diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py index d2aad13..753026a 100644 --- a/dashboard/windc/tables.py +++ b/dashboard/windc/tables.py @@ -43,14 +43,9 @@ ACTIVE_STATES = ("ACTIVE",) POWER_STATES = { 0: "NO STATE", 1: "RUNNING", - 2: "BLOCKED", - 3: "PAUSED", - 4: "SHUTDOWN", - 5: "SHUTOFF", - 6: "CRASHED", - 7: "SUSPENDED", - 8: "FAILED", - 9: "BUILDING", + 2: "SHUTDOWN", + 3: "FAILED", + 4: "BUILDING", } PAUSE = 0 @@ -205,13 +200,6 @@ def get_size(instance): return _("Not available") -def get_keyname(instance): - if hasattr(instance, "key_name"): - keyname = instance.key_name - return keyname - return _("Not available") - - def get_power_state(instance): return POWER_STATES.get(getattr(instance, "OS-EXT-STS:power_state", 0), '') @@ -252,7 +240,6 @@ class WinDCTable(tables.DataTable): size = tables.Column(get_size, verbose_name=_("Type"), attrs={'data-type': 'type'}) - keypair = tables.Column(get_keyname, verbose_name=_("Keypair")) status = tables.Column("status", filters=(title, replace_underscores), verbose_name=_("Status"), diff --git a/dashboard/windc/templates/windc/create.html b/dashboard/windc/templates/windc/create.html index cc5e244..09c996a 100644 --- a/dashboard/windc/templates/windc/create.html +++ b/dashboard/windc/templates/windc/create.html @@ -1,9 +1,9 @@ {% extends 'base.html' %} {% load i18n %} -{% block title %}{% trans "Create WinDC" %}{% endblock %} +{% block title %}{% trans "Create Windows Instance" %}{% endblock %} {% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("CreateWinDC") %} + {% include "horizon/common/_page_header.html" with title=_("Create Windows Instance") %} {% endblock page_header %} {% block main %} diff --git a/dashboard/windc/templates/windc/index.html b/dashboard/windc/templates/windc/index.html index 13d24d8..1ab2736 100644 --- a/dashboard/windc/templates/windc/index.html +++ b/dashboard/windc/templates/windc/index.html @@ -1,9 +1,9 @@ {% extends 'base.html' %} {% load i18n %} -{% block title %}{% trans "WinDC" %}{% endblock %} +{% block title %}{% trans "Windows Services" %}{% endblock %} {% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("WinDC") %} + {% include "horizon/common/_page_header.html" with title=_("Windows Services") %} {% endblock page_header %} {% block main %} diff --git a/dashboard/windc/urls.py b/dashboard/windc/urls.py index ec6314c..0399b4b 100644 --- a/dashboard/windc/urls.py +++ b/dashboard/windc/urls.py @@ -27,5 +27,5 @@ VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views' urlpatterns = patterns(VIEW_MOD, url(r'^$', IndexView.as_view(), name='index'), - url(r'^create/$', CreateWinDCView.as_view(), name='CreateWinDC') + url(r'create/$', CreateWinDCView.as_view(), name='CreateWinDC') ) diff --git a/dashboard/windc/views.py b/dashboard/windc/views.py index a5fd2f6..a61fb2b 100644 --- a/dashboard/windc/views.py +++ b/dashboard/windc/views.py @@ -36,8 +36,6 @@ from horizon import tables from horizon import workflows from openstack_dashboard import api -from .forms import UpdateInstance -from .tabs import InstanceDetailTabs from .tables import WinDCTable from .workflows import CreateWinDC diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index a282eec..3328b1d 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -454,7 +454,7 @@ class SetNetwork(workflows.Step): class CreateWinDC(workflows.Workflow): slug = "create_windc" - name = _("Create WinDC Instance") + name = _("Create Windows Data Center Instance") finalize_button_name = _("Deploy") success_message = _('Deployed %(count)s named "%(name)s".') failure_message = _('Unable to deploy %(count)s named "%(name)s".') From 1b8aef4ee7421d8734bfd033996437049443ce7b Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Thu, 14 Feb 2013 18:25:03 +0400 Subject: [PATCH 03/21] Fixed small problems with links and titles on pages. --- dashboard/windc/urls.py | 2 +- dashboard/windc/workflows.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/windc/urls.py b/dashboard/windc/urls.py index 0399b4b..19bd6c0 100644 --- a/dashboard/windc/urls.py +++ b/dashboard/windc/urls.py @@ -27,5 +27,5 @@ VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views' urlpatterns = patterns(VIEW_MOD, url(r'^$', IndexView.as_view(), name='index'), - url(r'create/$', CreateWinDCView.as_view(), name='CreateWinDC') + url(r'^create$', CreateWinDCView.as_view(), name='create') ) diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index 3328b1d..ce25b44 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -454,7 +454,7 @@ class SetNetwork(workflows.Step): class CreateWinDC(workflows.Workflow): slug = "create_windc" - name = _("Create Windows Data Center Instance") + name = _("Create Windows Service") finalize_button_name = _("Deploy") success_message = _('Deployed %(count)s named "%(name)s".') failure_message = _('Unable to deploy %(count)s named "%(name)s".') From 352ab3d2280e7af36f29fe07b5fdee089d49a18d Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Fri, 15 Feb 2013 10:54:11 +0400 Subject: [PATCH 04/21] Added simple form for configuration Domen Controllers and IIS Servers. --- dashboard/ReadMe.txt | 4 +- dashboard/windc/forms.py | 2 +- dashboard/windc/panel.py | 2 +- dashboard/windc/tables.py | 79 +--- dashboard/windc/tabs.py | 85 ---- dashboard/windc/templates/windc/create.html | 4 +- dashboard/windc/urls.py | 4 +- dashboard/windc/views.py | 12 +- dashboard/windc/workflows.py | 479 ++------------------ 9 files changed, 85 insertions(+), 586 deletions(-) delete mode 100644 dashboard/windc/tabs.py diff --git a/dashboard/ReadMe.txt b/dashboard/ReadMe.txt index 39e951e..6678e44 100644 --- a/dashboard/ReadMe.txt +++ b/dashboard/ReadMe.txt @@ -1,7 +1,5 @@ # TO DO: -# 1. Fix issue with Create button -# 2. Create simple form for Windows Data Center deploy -# 3. Remove extra code +# 1. Remove extra code # This file is described how to install new tab on horizon dashboard. diff --git a/dashboard/windc/forms.py b/dashboard/windc/forms.py index 518a9a0..5eb53df 100644 --- a/dashboard/windc/forms.py +++ b/dashboard/windc/forms.py @@ -46,7 +46,7 @@ class UpdateInstance(forms.SelfHandlingForm): _('Instance "%s" updated.') % data['name']) return server except: - redirect = reverse("horizon:project:instances:index") + redirect = reverse("horizon:project:windc:index") exceptions.handle(request, _('Unable to update instance.'), redirect=redirect) diff --git a/dashboard/windc/panel.py b/dashboard/windc/panel.py index cc464af..92bad57 100644 --- a/dashboard/windc/panel.py +++ b/dashboard/windc/panel.py @@ -22,7 +22,7 @@ from openstack_dashboard.dashboards.project import dashboard class WinDC(horizon.Panel): - name = _("WinDC") + name = _("Windows Services") slug = 'windc' diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py index 753026a..3875f06 100644 --- a/dashboard/windc/tables.py +++ b/dashboard/windc/tables.py @@ -33,7 +33,6 @@ from horizon.utils.filters import replace_underscores from openstack_dashboard import api from openstack_dashboard.dashboards.project.access_and_security \ .floating_ips.workflows import IPAssociationWorkflow -from .tabs import InstanceDetailTabs, LogTab, ConsoleTab LOG = logging.getLogger(__name__) @@ -61,12 +60,12 @@ def is_deleting(instance): return task_state.lower() == "deleting" -class RebootWinDC(tables.BatchAction): +class RebootService(tables.BatchAction): name = "reboot" action_present = _("Reboot") action_past = _("Rebooted") - data_type_singular = _("Instance") - data_type_plural = _("Instances") + data_type_singular = _("Service") + data_type_plural = _("Services") classes = ('btn-danger', 'btn-reboot') def allowed(self, request, instance=None): @@ -78,9 +77,9 @@ class RebootWinDC(tables.BatchAction): api.nova.server_reboot(request, obj_id) -class CreateWinDC(tables.LinkAction): - name = "CreateWinDC" - verbose_name = _("Create WinDC") +class CreateService(tables.LinkAction): + name = "CreateService" + verbose_name = _("Create Windows Service") url = "horizon:project:windc:create" classes = ("btn-launch", "ajax-modal") @@ -101,7 +100,7 @@ class CreateWinDC(tables.LinkAction): self.verbose_name = string_concat(self.verbose_name, ' ', _("(Quota exceeded)")) else: - self.verbose_name = _("Create WinDC") + self.verbose_name = _("Create Windows Service") classes = [c for c in self.classes if c != "disabled"] self.classes = classes except: @@ -112,12 +111,12 @@ class CreateWinDC(tables.LinkAction): return True # The action should always be displayed -class DeleteWinDC(tables.BatchAction): - name = "DeleteWinDC" - action_present = _("DeleteWinDC") +class DeleteService(tables.BatchAction): + name = "DeleteService" + action_present = _("DeleteService") action_past = _("Scheduled termination of") - data_type_singular = _("Instance") - data_type_plural = _("Instances") + data_type_singular = _("Service") + data_type_plural = _("Services") classes = ('btn-danger', 'btn-terminate') def allowed(self, request, instance=None): @@ -132,46 +131,15 @@ class DeleteWinDC(tables.BatchAction): api.nova.server_delete(request, obj_id) -class EditWinDC(tables.LinkAction): +class EditService(tables.LinkAction): name = "edit" - verbose_name = _("Edit Instance") - url = "horizon:project:instances:update" + verbose_name = _("Edit Service") + url = "horizon:project:windc:update" classes = ("ajax-modal", "btn-edit") def allowed(self, request, instance): return not is_deleting(instance) - -class ConsoleLink(tables.LinkAction): - name = "console" - verbose_name = _("Console") - url = "horizon:project:instances:detail" - classes = ("btn-console",) - - def allowed(self, request, instance=None): - return instance.status in ACTIVE_STATES and not is_deleting(instance) - - def get_link_url(self, datum): - base_url = super(ConsoleLink, self).get_link_url(datum) - tab_query_string = ConsoleTab(InstanceDetailTabs).get_query_string() - return "?".join([base_url, tab_query_string]) - - -class LogLink(tables.LinkAction): - name = "log" - verbose_name = _("View Log") - url = "horizon:project:instances:detail" - classes = ("btn-log",) - - def allowed(self, request, instance=None): - return instance.status in ACTIVE_STATES and not is_deleting(instance) - - def get_link_url(self, datum): - base_url = super(LogLink, self).get_link_url(datum) - tab_query_string = LogTab(InstanceDetailTabs).get_query_string() - return "?".join([base_url, tab_query_string]) - - class UpdateRow(tables.Row): ajax = True @@ -183,7 +151,7 @@ class UpdateRow(tables.Row): def get_ips(instance): - template_name = 'project/instances/_instance_ips.html' + template_name = 'project/windc/_instance_ips.html' context = {"instance": instance} return template.loader.render_to_string(template_name, context) @@ -223,7 +191,7 @@ TASK_DISPLAY_CHOICES = ( ) -class WinDCTable(tables.DataTable): +class WinServicesTable(tables.DataTable): TASK_STATUS_CHOICES = ( (None, True), ("none", True) @@ -234,8 +202,8 @@ class WinDCTable(tables.DataTable): ("error", False), ) name = tables.Column("name", - link=("horizon:project:instances:detail"), - verbose_name=_("WinDC Instance Name")) + link=("horizon:project:windc:detail"), + verbose_name=_("Name")) ip = tables.Column(get_ips, verbose_name=_("IP Address")) size = tables.Column(get_size, verbose_name=_("Type"), @@ -252,14 +220,11 @@ class WinDCTable(tables.DataTable): status=True, status_choices=TASK_STATUS_CHOICES, display_choices=TASK_DISPLAY_CHOICES) - state = tables.Column(get_power_state, - filters=(title, replace_underscores), - verbose_name=_("Power State")) class Meta: name = "windc" - verbose_name = _("WinDC") + verbose_name = _("Windows Services") status_columns = ["status", "task"] row_class = UpdateRow - table_actions = (CreateWinDC, DeleteWinDC) - row_actions = (EditWinDC, ConsoleLink, LogLink, RebootWinDC) + table_actions = (CreateService, DeleteService) + row_actions = (EditService, RebootService) diff --git a/dashboard/windc/tabs.py b/dashboard/windc/tabs.py deleted file mode 100644 index 029c877..0000000 --- a/dashboard/windc/tabs.py +++ /dev/null @@ -1,85 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 Nebula, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import tabs - -from openstack_dashboard import api - - -class OverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = ("project/instances/" - "_detail_overview.html") - - def get_context_data(self, request): - return {"instance": self.tab_group.kwargs['instance']} - - -class LogTab(tabs.Tab): - name = _("Log") - slug = "log" - template_name = "project/instances/_detail_log.html" - preload = False - - def get_context_data(self, request): - instance = self.tab_group.kwargs['instance'] - try: - data = api.nova.server_console_output(request, - instance.id, - tail_length=35) - except: - data = _('Unable to get log for instance "%s".') % instance.id - exceptions.handle(request, ignore=True) - return {"instance": instance, - "console_log": data} - - -class ConsoleTab(tabs.Tab): - name = _("Console") - slug = "console" - template_name = "project/instances/_detail_console.html" - preload = False - - def get_context_data(self, request): - instance = self.tab_group.kwargs['instance'] - # Currently prefer VNC over SPICE, since noVNC has had much more - # testing than spice-html5 - try: - console = api.nova.server_vnc_console(request, instance.id) - console_url = "%s&title=%s(%s)" % ( - console.url, - getattr(instance, "name", ""), - instance.id) - except: - try: - console = api.nova.server_spice_console(request, instance.id) - console_url = "%s&title=%s(%s)" % ( - console.url, - getattr(instance, "name", ""), - instance.id) - except: - console_url = None - return {'console_url': console_url, 'instance_id': instance.id} - - -class InstanceDetailTabs(tabs.TabGroup): - slug = "instance_details" - tabs = (OverviewTab, LogTab, ConsoleTab) - sticky = True diff --git a/dashboard/windc/templates/windc/create.html b/dashboard/windc/templates/windc/create.html index 09c996a..0508b68 100644 --- a/dashboard/windc/templates/windc/create.html +++ b/dashboard/windc/templates/windc/create.html @@ -1,9 +1,9 @@ {% extends 'base.html' %} {% load i18n %} -{% block title %}{% trans "Create Windows Instance" %}{% endblock %} +{% block title %}{% trans "Create Windows Service" %}{% endblock %} {% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Create Windows Instance") %} + {% include "horizon/common/_page_header.html" with title=_("Create Windows Service") %} {% endblock page_header %} {% block main %} diff --git a/dashboard/windc/urls.py b/dashboard/windc/urls.py index 19bd6c0..8f52b6f 100644 --- a/dashboard/windc/urls.py +++ b/dashboard/windc/urls.py @@ -20,12 +20,12 @@ from django.conf.urls.defaults import patterns, url -from .views import IndexView, CreateWinDCView +from .views import IndexView, CreateWinServiceView VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views' urlpatterns = patterns(VIEW_MOD, url(r'^$', IndexView.as_view(), name='index'), - url(r'^create$', CreateWinDCView.as_view(), name='create') + url(r'^create$', CreateWinServiceView.as_view(), name='create') ) diff --git a/dashboard/windc/views.py b/dashboard/windc/views.py index a61fb2b..c5854e0 100644 --- a/dashboard/windc/views.py +++ b/dashboard/windc/views.py @@ -36,15 +36,15 @@ from horizon import tables from horizon import workflows from openstack_dashboard import api -from .tables import WinDCTable -from .workflows import CreateWinDC +from .tables import WinServicesTable +from .workflows import CreateWinService LOG = logging.getLogger(__name__) class IndexView(tables.DataTableView): - table_class = WinDCTable + table_class = WinServicesTable template_name = 'project/windc/index.html' def get_data(self): @@ -82,12 +82,12 @@ class IndexView(tables.DataTableView): return instances -class CreateWinDCView(workflows.WorkflowView): - workflow_class = CreateWinDC +class CreateWinServiceView(workflows.WorkflowView): + workflow_class = CreateWinService template_name = "project/windc/create.html" def get_initial(self): - initial = super(CreateWinDCView, self).get_initial() + initial = super(CreateWinServiceView, self).get_initial() initial['project_id'] = self.request.user.tenant_id initial['user_id'] = self.request.user.id return initial diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index ce25b44..292a643 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -64,451 +64,72 @@ class SelectProjectUser(workflows.Step): contributes = ("project_id", "user_id") -class VolumeOptionsAction(workflows.Action): - VOLUME_CHOICES = ( - ('', _("Don't boot from a volume.")), - ("volume_id", _("Boot from volume.")), - ("volume_snapshot_id", _("Boot from volume snapshot " - "(creates a new volume).")), - ) - # Boot from volume options - volume_type = forms.ChoiceField(label=_("Volume Options"), - choices=VOLUME_CHOICES, - required=False) - volume_id = forms.ChoiceField(label=_("Volume"), required=False) - volume_snapshot_id = forms.ChoiceField(label=_("Volume Snapshot"), - required=False) - device_name = forms.CharField(label=_("Device Name"), - required=False, - initial="vda", - help_text=_("Volume mount point (e.g. 'vda' " - "mounts at '/dev/vda').")) - delete_on_terminate = forms.BooleanField(label=_("Delete on Terminate"), - initial=False, - required=False, - help_text=_("Delete volume on " - "instance terminate")) +class ConfigureWinDCAction(workflows.Action): + dc_name = forms.CharField(label=_("Domain Name"), + required=False, + help_text=_("A name of new domain.")) + + dc_count = forms.IntegerField(label=_("Domain Controllers Count"), + required=True, + min_value=1, + max_value=100, + initial=1, + help_text=_("Domain Controllers count.")) class Meta: - name = _("Volume Options") - permissions = ('openstack.services.volume',) - help_text_template = ("project/instances/" - "_launch_volumes_help.html") - - def clean(self): - cleaned_data = super(VolumeOptionsAction, self).clean() - volume_opt = cleaned_data.get('volume_type', None) - - if volume_opt and not cleaned_data[volume_opt]: - raise forms.ValidationError(_('Please choose a volume, or select ' - '%s.') % self.VOLUME_CHOICES[0][1]) - return cleaned_data - - def _get_volume_display_name(self, volume): - if hasattr(volume, "volume_id"): - vol_type = "snap" - visible_label = _("Snapshot") - else: - vol_type = "vol" - visible_label = _("Volume") - return (("%s:%s" % (volume.id, vol_type)), - ("%s - %s GB (%s)" % (volume.display_name, - volume.size, - visible_label))) - - def populate_volume_id_choices(self, request, context): - volume_options = [("", _("Select Volume"))] - try: - volumes = [v for v in cinder.volume_list(self.request) - if v.status == api.cinder.VOLUME_STATE_AVAILABLE] - volume_options.extend([self._get_volume_display_name(vol) - for vol in volumes]) - except: - exceptions.handle(self.request, - _('Unable to retrieve list of volumes.')) - return volume_options - - def populate_volume_snapshot_id_choices(self, request, context): - volume_options = [("", _("Select Volume Snapshot"))] - try: - snapshots = cinder.volume_snapshot_list(self.request) - snapshots = [s for s in snapshots - if s.status == api.cinder.VOLUME_STATE_AVAILABLE] - volume_options.extend([self._get_volume_display_name(snap) - for snap in snapshots]) - except: - exceptions.handle(self.request, - _('Unable to retrieve list of volume ' - 'snapshots.')) - - return volume_options + name = _("Domain Controllers") + help_text_template = ("project/windc/_dc_help.html") -class VolumeOptions(workflows.Step): - action_class = VolumeOptionsAction - depends_on = ("project_id", "user_id") - contributes = ("volume_type", - "volume_id", - "device_name", # Can be None for an image. - "delete_on_terminate") - - def contribute(self, data, context): - context = super(VolumeOptions, self).contribute(data, context) - # Translate form input to context for volume values. - if "volume_type" in data and data["volume_type"]: - context['volume_id'] = data.get(data['volume_type'], None) - - if not context.get("volume_type", ""): - context['volume_type'] = self.action.VOLUME_CHOICES[0][0] - context['volume_id'] = None - context['device_name'] = None - context['delete_on_terminate'] = None - return context +class ConfigureWinDC(workflows.Step): + action_class = ConfigureWinDCAction + #contributes = ("windows_domain_controller",) -class SetInstanceDetailsAction(workflows.Action): - SOURCE_TYPE_CHOICES = ( - ("image_id", _("Image")), - ("instance_snapshot_id", _("Snapshot")), - ) - source_type = forms.ChoiceField(label=_("Instance Source"), - choices=SOURCE_TYPE_CHOICES) - image_id = forms.ChoiceField(label=_("Image"), required=False) - instance_snapshot_id = forms.ChoiceField(label=_("Instance Snapshot"), - required=False) - name = forms.CharField(max_length=80, label=_("Instance Name")) - flavor = forms.ChoiceField(label=_("Flavor"), - help_text=_("Size of image to launch.")) - count = forms.IntegerField(label=_("Instance Count"), - min_value=1, - initial=1, - help_text=_("Number of instances to launch.")) +class ConfigureWinIISAction(workflows.Action): + iis_name = forms.CharField(label=_("IIS Server Name"), + required=False, + help_text=_("A name of IIS Server.")) + + iis_count = forms.IntegerField(label=_("IIS Servers Count"), + required=True, + min_value=1, + max_value=100, + initial=1, + help_text=_("IIS Servers count.")) + + iis_domain = forms.CharField(label=_("Member of the Domain"), + required=False, + help_text=_("A name of domain for" + " IIS Server.")) class Meta: - name = _("Details") - help_text_template = ("project/instances/" - "_launch_details_help.html") - - def clean(self): - cleaned_data = super(SetInstanceDetailsAction, self).clean() - - # Validate our instance source. - source = cleaned_data['source_type'] - # There should always be at least one image_id choice, telling the user - # that there are "No Images Available" so we check for 2 here... - if source == 'image_id' and not \ - filter(lambda x: x[0] != '', self.fields['image_id'].choices): - raise forms.ValidationError(_("There are no image sources " - "available; you must first create " - "an image before attempting to " - "launch an instance.")) - if not cleaned_data[source]: - raise forms.ValidationError(_("Please select an option for the " - "instance source.")) - - # Prevent launching multiple instances with the same volume. - # TODO(gabriel): is it safe to launch multiple instances with - # a snapshot since it should be cloned to new volumes? - count = cleaned_data.get('count', 1) - volume_type = self.data.get('volume_type', None) - if volume_type and count > 1: - msg = _('Launching multiple instances is only supported for ' - 'images and instance snapshots.') - raise forms.ValidationError(msg) - - return cleaned_data - - def _get_available_images(self, request, context): - project_id = context.get('project_id', None) - if not hasattr(self, "_public_images"): - public = {"is_public": True, - "status": "active"} - try: - public_images, _more = glance.image_list_detailed( - request, filters=public) - except: - public_images = [] - exceptions.handle(request, - _("Unable to retrieve public images.")) - self._public_images = public_images - - # Preempt if we don't have a project_id yet. - if project_id is None: - setattr(self, "_images_for_%s" % project_id, []) - - if not hasattr(self, "_images_for_%s" % project_id): - owner = {"property-owner_id": project_id, - "status": "active"} - try: - owned_images, _more = glance.image_list_detailed( - request, filters=owner) - except: - exceptions.handle(request, - _("Unable to retrieve images for " - "the current project.")) - setattr(self, "_images_for_%s" % project_id, owned_images) - - owned_images = getattr(self, "_images_for_%s" % project_id) - images = owned_images + self._public_images - - # Remove duplicate images - image_ids = [] - final_images = [] - for image in images: - if image.id not in image_ids: - image_ids.append(image.id) - final_images.append(image) - return [image for image in final_images - if image.container_format not in ('aki', 'ari')] - - def populate_image_id_choices(self, request, context): - images = self._get_available_images(request, context) - choices = [(image.id, image.name) - for image in images - if image.properties.get("image_type", '') != "snapshot"] - if choices: - choices.insert(0, ("", _("Select Image"))) - else: - choices.insert(0, ("", _("No images available."))) - return choices - - def populate_instance_snapshot_id_choices(self, request, context): - images = self._get_available_images(request, context) - choices = [(image.id, image.name) - for image in images - if image.properties.get("image_type", '') == "snapshot"] - if choices: - choices.insert(0, ("", _("Select Instance Snapshot"))) - else: - choices.insert(0, ("", _("No snapshots available."))) - return choices - - def populate_flavor_choices(self, request, context): - try: - flavors = api.nova.flavor_list(request) - flavor_list = [(flavor.id, "%s" % flavor.name) - for flavor in flavors] - except: - flavor_list = [] - exceptions.handle(request, - _('Unable to retrieve instance flavors.')) - return sorted(flavor_list) - - def get_help_text(self): - extra = {} - try: - extra['usages'] = quotas.tenant_quota_usages(self.request) - extra['usages_json'] = json.dumps(extra['usages']) - flavors = json.dumps([f._info for f in - api.nova.flavor_list(self.request)]) - extra['flavors'] = flavors - except: - exceptions.handle(self.request, - _("Unable to retrieve quota information.")) - return super(SetInstanceDetailsAction, self).get_help_text(extra) + name = _("Internet Information Services") + help_text_template = ("project/windc/_iis_help.html") -class SetInstanceDetails(workflows.Step): - action_class = SetInstanceDetailsAction - contributes = ("source_type", "source_id", "name", "count", "flavor") +class ConfigureWinIIS(workflows.Step): + action_class = ConfigureWinIISAction + #contributes = ("windows_iis",) - def prepare_action_context(self, request, context): - if 'source_type' in context and 'source_id' in context: - context[context['source_type']] = context['source_id'] - return context - - def contribute(self, data, context): - context = super(SetInstanceDetails, self).contribute(data, context) - # Allow setting the source dynamically. - if ("source_type" in context and "source_id" in context - and context["source_type"] not in context): - context[context["source_type"]] = context["source_id"] - - # Translate form input to context for source values. - if "source_type" in data: - context["source_id"] = data.get(data['source_type'], None) - - return context - - -KEYPAIR_IMPORT_URL = "horizon:project:access_and_security:keypairs:import" - - -class SetAccessControlsAction(workflows.Action): - keypair = forms.DynamicChoiceField(label=_("Keypair"), - required=False, - help_text=_("Which keypair to use for " - "authentication."), - add_item_link=KEYPAIR_IMPORT_URL) - groups = forms.MultipleChoiceField(label=_("Security Groups"), - required=True, - initial=["default"], - widget=forms.CheckboxSelectMultiple(), - help_text=_("Launch instance in these " - "security groups.")) - - class Meta: - name = _("Access & Security") - help_text = _("Control access to your instance via keypairs, " - "security groups, and other mechanisms.") - - def populate_keypair_choices(self, request, context): - try: - keypairs = api.nova.keypair_list(request) - keypair_list = [(kp.name, kp.name) for kp in keypairs] - except: - keypair_list = [] - exceptions.handle(request, - _('Unable to retrieve keypairs.')) - if keypair_list: - keypair_list.insert(0, ("", _("Select a keypair"))) - else: - keypair_list = (("", _("No keypairs available.")),) - return keypair_list - - def populate_groups_choices(self, request, context): - try: - groups = api.nova.security_group_list(request) - security_group_list = [(sg.name, sg.name) for sg in groups] - except: - exceptions.handle(request, - _('Unable to retrieve list of security groups')) - security_group_list = [] - return security_group_list - - -class SetAccessControls(workflows.Step): - action_class = SetAccessControlsAction - depends_on = ("project_id", "user_id") - contributes = ("keypair_id", "security_group_ids") - - def contribute(self, data, context): - if data: - post = self.workflow.request.POST - context['security_group_ids'] = post.getlist("groups") - context['keypair_id'] = data.get("keypair", "") - return context - - -class CustomizeAction(workflows.Action): - customization_script = forms.CharField(widget=forms.Textarea, - label=_("Customization Script"), - required=False, - help_text=_("A script or set of " - "commands to be " - "executed after the " - "instance has been " - "built (max 16kb).")) - - class Meta: - name = _("Post-Creation") - help_text_template = ("project/instances/" - "_launch_customize_help.html") - - -class PostCreationStep(workflows.Step): - action_class = CustomizeAction - contributes = ("customization_script",) - - -class SetNetworkAction(workflows.Action): - network = forms.MultipleChoiceField(label=_("Networks"), - required=True, - widget=forms.CheckboxSelectMultiple(), - help_text=_("Launch instance with" - "these networks")) - - class Meta: - name = _("Networking") - permissions = ('openstack.services.network',) - help_text = _("Select networks for your instance.") - - def populate_network_choices(self, request, context): - try: - tenant_id = self.request.user.tenant_id - networks = api.quantum.network_list_for_tenant(request, tenant_id) - for n in networks: - n.set_id_as_name_if_empty() - network_list = [(network.id, network.name) for network in networks] - except: - network_list = [] - exceptions.handle(request, - _('Unable to retrieve networks.')) - return network_list - - -class SetNetwork(workflows.Step): - action_class = SetNetworkAction - contributes = ("network_id",) - - def contribute(self, data, context): - if data: - networks = self.workflow.request.POST.getlist("network") - # If no networks are explicitly specified, network list - # contains an empty string, so remove it. - networks = [n for n in networks if n != ''] - if networks: - context['network_id'] = networks - return context - - -class CreateWinDC(workflows.Workflow): - slug = "create_windc" +class CreateWinService(workflows.Workflow): + slug = "create" name = _("Create Windows Service") finalize_button_name = _("Deploy") success_message = _('Deployed %(count)s named "%(name)s".') failure_message = _('Unable to deploy %(count)s named "%(name)s".') success_url = "horizon:project:windc:index" default_steps = (SelectProjectUser, - SetInstanceDetails, - SetAccessControls, - SetNetwork, - VolumeOptions, - PostCreationStep) + ConfigureWinDC, + ConfigureWinIIS) - def format_status_message(self, message): - name = self.context.get('name', 'unknown instance') - count = self.context.get('count', 1) - if int(count) > 1: - return message % {"count": _("%s instances") % count, - "name": name} - else: - return message % {"count": _("instance"), "name": name} + ## TO DO: + ## Need to rewrite the following code: - def handle(self, request, context): - custom_script = context.get('customization_script', '') - - # Determine volume mapping options - if context.get('volume_type', None): - if(context['delete_on_terminate']): - del_on_terminate = 1 - else: - del_on_terminate = 0 - mapping_opts = ("%s::%s" - % (context['volume_id'], del_on_terminate)) - dev_mapping = {context['device_name']: mapping_opts} - else: - dev_mapping = None - - netids = context.get('network_id', None) - if netids: - nics = [{"net-id": netid, "v4-fixed-ip": ""} - for netid in netids] - else: - nics = None - - try: - api.nova.server_create(request, - context['name'], - context['source_id'], - context['flavor'], - context['keypair_id'], - normalize_newlines(custom_script), - context['security_group_ids'], - dev_mapping, - nics=nics, - instance_count=int(context['count'])) - return True - except: - exceptions.handle(request) - return False + #def handle(self, request, context): + # try: + # api.windc.create(request,...) + # return True + # except: + # exceptions.handle(request) + # return False From 766fe43ccee908700a1ee89046f5fee473a9205f Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Fri, 15 Feb 2013 14:24:50 +0400 Subject: [PATCH 05/21] Added additional fields for Domain Controller. --- dashboard/windc/templates/windc/_dc_help.html | 2 ++ .../windc/templates/windc/_iis_help.html | 2 ++ dashboard/windc/workflows.py | 21 ++++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 dashboard/windc/templates/windc/_dc_help.html create mode 100644 dashboard/windc/templates/windc/_iis_help.html diff --git a/dashboard/windc/templates/windc/_dc_help.html b/dashboard/windc/templates/windc/_dc_help.html new file mode 100644 index 0000000..1cb4efc --- /dev/null +++ b/dashboard/windc/templates/windc/_dc_help.html @@ -0,0 +1,2 @@ +{% load i18n %} +

{% blocktrans %}You can deploy few domain controllers with one name.{% endblocktrans %}

\ No newline at end of file diff --git a/dashboard/windc/templates/windc/_iis_help.html b/dashboard/windc/templates/windc/_iis_help.html new file mode 100644 index 0000000..e8004c8 --- /dev/null +++ b/dashboard/windc/templates/windc/_iis_help.html @@ -0,0 +1,2 @@ +{% load i18n %} +

{% blocktrans %}You can deploy few Internet Information Services in one domain.{% endblocktrans %}

\ No newline at end of file diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index 292a643..385da51 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -61,7 +61,6 @@ class SelectProjectUserAction(workflows.Action): class SelectProjectUser(workflows.Step): action_class = SelectProjectUserAction - contributes = ("project_id", "user_id") class ConfigureWinDCAction(workflows.Action): @@ -69,6 +68,10 @@ class ConfigureWinDCAction(workflows.Action): required=False, help_text=_("A name of new domain.")) + dc_net_name = forms.CharField(label=_("Domain NetBIOS Name"), + required=False, + help_text=_("A NetBIOS name of new domain.")) + dc_count = forms.IntegerField(label=_("Domain Controllers Count"), required=True, min_value=1, @@ -76,6 +79,19 @@ class ConfigureWinDCAction(workflows.Action): initial=1, help_text=_("Domain Controllers count.")) + adm_password = forms.CharField(widget=forms.PasswordInput, + label=_("Administrator password"), + required=False, + help_text=_("Password for " + "administrator account.")) + + recovery_password = forms.CharField(widget=forms.PasswordInput, + label=_("Recovery password"), + required=False, + help_text=_("Password for " + "Active Directory " + "Recovery Mode.")) + class Meta: name = _("Domain Controllers") help_text_template = ("project/windc/_dc_help.html") @@ -83,7 +99,6 @@ class ConfigureWinDCAction(workflows.Action): class ConfigureWinDC(workflows.Step): action_class = ConfigureWinDCAction - #contributes = ("windows_domain_controller",) class ConfigureWinIISAction(workflows.Action): @@ -110,7 +125,7 @@ class ConfigureWinIISAction(workflows.Action): class ConfigureWinIIS(workflows.Step): action_class = ConfigureWinIISAction - #contributes = ("windows_iis",) + class CreateWinService(workflows.Workflow): slug = "create" From 61801a0ca8a2607b5d922e51d4c86dd93082847a Mon Sep 17 00:00:00 2001 From: Georgy Okrokvertskhov Date: Fri, 15 Feb 2013 06:04:29 -0800 Subject: [PATCH 06/21] 1. Added support of CloudFormation templates. Made a simple interface to build template. Stan can work here to redesign template.py 2. Added calls of drivers. Now heat is called from cmd instead of client. Should be rewritten. 3. ActiveDirectory makes a static template. Need to rewrite this with working with actual parameters. --- windc/heat_run | 5 + windc/tests/manual/createServiceParameters | 2 +- windc/tools/pip-requires | 2 +- windc/windc/adapters/openstack.py | 19 ++++ windc/windc/core/builder.py | 4 + windc/windc/core/builders/ActiveDirectory.py | 61 +++++++++++ windc/windc/core/change_events.py | 11 +- windc/windc/core/commands.py | 33 ++++++ windc/windc/core/templates.py | 107 +++++++++++++++++++ windc/windc/drivers/command_executor.py | 37 +++++++ windc/windc/drivers/openstack_heat.py | 38 +++++++ 11 files changed, 314 insertions(+), 5 deletions(-) create mode 100755 windc/heat_run create mode 100644 windc/windc/adapters/openstack.py create mode 100644 windc/windc/core/commands.py create mode 100644 windc/windc/core/templates.py create mode 100644 windc/windc/drivers/command_executor.py create mode 100644 windc/windc/drivers/openstack_heat.py diff --git a/windc/heat_run b/windc/heat_run new file mode 100755 index 0000000..69e4182 --- /dev/null +++ b/windc/heat_run @@ -0,0 +1,5 @@ +#!/bin/bash + +source openrc.sh +heat "$@" + diff --git a/windc/tests/manual/createServiceParameters b/windc/tests/manual/createServiceParameters index 6e9817c..0954f92 100644 --- a/windc/tests/manual/createServiceParameters +++ b/windc/tests/manual/createServiceParameters @@ -4,5 +4,5 @@ "domain": "ACME.cloud", "AdminUser": "Admin", "AdminPassword": "StrongPassword", -"DomainControllerNames": ["APP-AD001","APP-AD002"] +"DomainControllerNames": ["AD-DC001"] } diff --git a/windc/tools/pip-requires b/windc/tools/pip-requires index 0cb916b..70f7e25 100644 --- a/windc/tools/pip-requires +++ b/windc/tools/pip-requires @@ -15,7 +15,7 @@ sqlalchemy-migrate>=0.7.2 httplib2 kombu iso8601>=0.1.4 - +PyChef # For paste.util.template used in keystone.common.template Paste diff --git a/windc/windc/adapters/openstack.py b/windc/windc/adapters/openstack.py new file mode 100644 index 0000000..9ca6733 --- /dev/null +++ b/windc/windc/adapters/openstack.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from heatclient import Client + diff --git a/windc/windc/core/builder.py b/windc/windc/core/builder.py index 2dc68e8..15a14e6 100644 --- a/windc/windc/core/builder.py +++ b/windc/windc/core/builder.py @@ -29,5 +29,9 @@ class Builder: def build(self, context, event, data): pass +def create_context(): + context = {} + context['commands']=[] + return context diff --git a/windc/windc/core/builders/ActiveDirectory.py b/windc/windc/core/builders/ActiveDirectory.py index 7181329..703703b 100644 --- a/windc/windc/core/builders/ActiveDirectory.py +++ b/windc/windc/core/builders/ActiveDirectory.py @@ -17,11 +17,14 @@ import logging +import uuid LOG = logging.getLogger(__name__) from windc.core.builder import Builder from windc.core import change_events as events from windc.db import api as db_api +from windc.core.templates import Template +from windc.core import commands as command_api class ActiveDirectory(Builder): def __init__(self): @@ -35,6 +38,8 @@ class ActiveDirectory(Builder): LOG.info ("Got service change event. Analysing..") if self.do_analysis(context, event, dc): self.plan_changes(context, event, dc) + + self.submit_commands(context, event, dc) else: LOG.debug("Not in my scope. Skip event.") pass @@ -44,10 +49,66 @@ class ActiveDirectory(Builder): zones = data['zones'] if data['type'] == self.type and len(zones) == 1: LOG.debug("It is a service which I should build.") + datacenter_id = data['datacenter_id'] + dc = db_api.datacenter_get(context['conf'],data['tenant_id'], + data['datacenter_id']) + datacenter = db_api.unpack_extra(dc) + context['stack_name']=datacenter['name'] return True else: return False def plan_changes(self, context, event, data): + # Here we can plan multiple command execution. + # It might be Heat call command, then chef call command and other + # + LOG.debug("Plan changes...") + self.prepare_template(context, event, data) + self.chef_configuration(context, event, data) + context['commands'].append(self.deploy_template_command(context, event, data)) + context['commands'].append(self.chef_configuration_command(context, event, data)) pass + def prepare_template(self, context, event, data): + LOG.debug("Prepare CloudFormation Template...") + template = Template() + template.add_description('Base template for Active Directory deployment') + sec_grp = template.create_security_group('Security group for AD') + rule = template.create_securitygroup_rule('tcp','3389','3389','0.0.0.0/0') + template.add_rule_to_securitygroup(sec_grp, rule) + template.add_resource('ADSecurityGroup', sec_grp) + + instance = template.create_instance() + instance_name= 'AD-DC001' + template.add_security_group(instance, 'ADSecurityGroup') + template.add_resource(instance_name, instance) + + template.add_output_value(instance_name+'-IP',{"Fn::GetAtt" : [instance_name,'PublicIp']}, + 'Public IP for the domain controller.') + context['template']=template + pass + + def deploy_template_command(self, context, event, data): + LOG.debug("Creating CloudFormation Template deployment command...") + fname = "templates/"+str(uuid.uuid4()) + f=open(fname, "w") + f.write(context['template'].to_json()) + f.close() + context['template_name']=fname + command = command_api.Command(command_api.TEMPLATE_DEPLOYMENT_COMMAND, context) + return command + pass + + def chef_configuration(self, context, event, data): + LOG.debug("Creating Chef configuration...") + context['Role'] = 'pdc' + pass + + def chef_configuration_command(self, context, event, data): + LOG.debug("Creating Chef configuration command...") + command = command_api.Command(command_api.CHEF_COMMAND, context) + return command + + def submit_commands(self, context, event, data): + LOG.debug("Submit commands for execution...") + pass \ No newline at end of file diff --git a/windc/windc/core/change_events.py b/windc/windc/core/change_events.py index 8324a7f..8955b58 100644 --- a/windc/windc/core/change_events.py +++ b/windc/windc/core/change_events.py @@ -20,6 +20,8 @@ import logging LOG = logging.getLogger(__name__) from windc.core import builder_set +from windc.core import builder +from windc.drivers import command_executor #Declare events types SCOPE_SERVICE_CHANGE = "Service" @@ -40,11 +42,14 @@ class Event: def change_event(conf, event, data): LOG.info("Change event of type: %s ", event) - context = {} + context = builder.create_context() context['conf'] = conf for builder_type in builder_set.builders.set: - builder = builder_set.builders.set[builder_type] - builder.build(context, event, data) + builder_instance = builder_set.builders.set[builder_type] + builder_instance.build(context, event, data) + + executor = command_executor.Executor() + executor.execute(context['commands']) pass diff --git a/windc/windc/core/commands.py b/windc/windc/core/commands.py new file mode 100644 index 0000000..089ec27 --- /dev/null +++ b/windc/windc/core/commands.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +TEMPLATE_DEPLOYMENT_COMMAND = "Template" +CHEF_COMMAND = "Chef" + +class Command: + type = "Empty" + context = None + + def __init__(self): + self.type = "Empty" + self.context = None + + def __init__(self, type, context): + self.type = type + self.context = context + + diff --git a/windc/windc/core/templates.py b/windc/windc/core/templates.py new file mode 100644 index 0000000..47a7c90 --- /dev/null +++ b/windc/windc/core/templates.py @@ -0,0 +1,107 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging +from windc.common.wsgi import JSONResponseSerializer +LOG = logging.getLogger(__name__) + +class Template: + def __init__(self): + self.content = {'AWSTemplateFormatVersion':'2010-09-09', 'Description':'', + 'Parameters':{}} + self.content['Mappings'] = { + "AWSInstanceType2Arch" : { + "t1.micro" : { "Arch" : "32" }, + "m1.small" : { "Arch" : "32" }, + "m1.large" : { "Arch" : "64" }, + "m1.xlarge" : { "Arch" : "64" }, + "m2.xlarge" : { "Arch" : "64" }, + "m2.2xlarge" : { "Arch" : "64" }, + "m2.4xlarge" : { "Arch" : "64" }, + "c1.medium" : { "Arch" : "32" }, + "c1.xlarge" : { "Arch" : "64" }, + "cc1.4xlarge" : { "Arch" : "64" } + }, + "DistroArch2AMI": { + "F16" : { "32" : "F16-i386-cfntools", "64" : "F16-x86_64-cfntools" }, + "F17" : { "32" : "F17-i386-cfntools", "64" : "F17-x86_64-cfntools" }, + "U10" : { "32" : "U10-i386-cfntools", "64" : "U10-x86_64-cfntools" }, + "RHEL-6.1": { "32" : "rhel61-i386-cfntools", "64" : "rhel61-x86_64-cfntools" }, + "RHEL-6.2": { "32" : "rhel62-i386-cfntools", "64" : "rhel62-x86_64-cfntools" }, + "RHEL-6.3": { "32" : "rhel63-i386-cfntools", "64" : "rhel63-x86_64-cfntools" } + } + } + self.content['Resources'] = {} + self.content['Outputs'] = {} + + def to_json(self): + serializer = JSONResponseSerializer() + json = serializer.to_json(self.content) + return json + + + def empty_template(self): + pass + + def add_description(self, description): + self.content['Description'] = description + + def add_parameter(self, name, parameter): + self.content['Parameters'].update({name : parameter}) + + def add_resource(self, name, resource): + self.content['Resources'].update({name : resource}) + + def create_parameter(self, defult, type, decription): + parameter = {'Default':default, 'Type':type, 'Description':description} + return parameter + + def create_security_group(self, description): + sec_grp = {'Type':'AWS::EC2::SecurityGroup'} + sec_grp['Properties'] = {} + sec_grp['Properties']['GroupDescription'] = description + sec_grp['Properties']['SecurityGroupIngress'] = [] + return sec_grp + + def add_rule_to_securitygroup(self, grp, rule): + grp['Properties']['SecurityGroupIngress'].append(rule) + + def create_securitygroup_rule(self, proto, f_port, t_port, cidr): + rule = {'IpProtocol':proto, 'FromPort':f_port, 'ToPort':t_port,'CidrIp': cidr} + return rule + + def create_instance(self): + instance = {'Type':'AWS::EC2::Instance','Metadata':{},'Properties':{}} + instance['Properties']['ImageId'] = 'U10-x86_64-cfntools' + instance['Properties']['SecurityGroups']=[] + instance['Properties']['KeyName'] = 'keero-linux-keys' + instance['Properties']['InstanceType'] = 'm1.small' + return instance + + def add_security_group(self, instance, grp_name): + instance['Properties']['SecurityGroups'].append({'Ref': grp_name}) + + def add_output_value(self, name, value, description): + self.content['Outputs'].update({name:{'Value':value, 'Description':description}}) + + def get_content(self): + return self.content + + + + diff --git a/windc/windc/drivers/command_executor.py b/windc/windc/drivers/command_executor.py new file mode 100644 index 0000000..c7c0d2f --- /dev/null +++ b/windc/windc/drivers/command_executor.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Piston Cloud Computing, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from windc.core import commands as commands_api +from windc.drivers import openstack_heat + +class Executor: + + map = {commands_api.TEMPLATE_DEPLOYMENT_COMMAND : openstack_heat.Heat} + + def __init__(self): + pass + + def execute(self, commands): + for command in commands: + if command.type == commands_api.TEMPLATE_DEPLOYMENT_COMMAND: + executor = openstack_heat.Heat() + executor.execute(command) + + diff --git a/windc/windc/drivers/openstack_heat.py b/windc/windc/drivers/openstack_heat.py new file mode 100644 index 0000000..7661bb6 --- /dev/null +++ b/windc/windc/drivers/openstack_heat.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Piston Cloud Computing, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +#from heatclient import Client +from subprocess import call + +import logging +LOG = logging.getLogger(__name__) + +class Heat: + + def __init__(self): + pass + + def execute(self, command): +# client = Client('1',OS_IMAGE_ENDPOINT, OS_TENANT_ID) + LOG.debug('Calling heat script to execute template') + call(["./heat_run","stack-create","-f "+command.context['template_name'], + command.context['stack_name']]) + pass + From 505c3da3a4fbc63748a562ad3a6352ac736a09cb Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Sat, 16 Feb 2013 03:10:02 -0800 Subject: [PATCH 07/21] Updated design. Removed extra code. --- dashboard/ReadMe.txt | 5 +- dashboard/windc/forms.py | 10 +- dashboard/windc/panel.py | 2 +- dashboard/windc/tables.py | 196 ++++++--------------- dashboard/windc/templates/windc/index.html | 4 +- dashboard/windc/urls.py | 7 +- dashboard/windc/views.py | 73 ++++---- dashboard/windc/workflows.py | 40 ++++- 8 files changed, 148 insertions(+), 189 deletions(-) diff --git a/dashboard/ReadMe.txt b/dashboard/ReadMe.txt index 6678e44..8d5cc0d 100644 --- a/dashboard/ReadMe.txt +++ b/dashboard/ReadMe.txt @@ -1,6 +1,7 @@ # TO DO: -# 1. Remove extra code -# +# 1. Add new functional for services and data centers +# 2. Fix issue with list of services: services table shoudl show services for +# specific data center This file is described how to install new tab on horizon dashboard. We should do the following: diff --git a/dashboard/windc/forms.py b/dashboard/windc/forms.py index 5eb53df..7c1329f 100644 --- a/dashboard/windc/forms.py +++ b/dashboard/windc/forms.py @@ -33,20 +33,20 @@ from horizon import messages LOG = logging.getLogger(__name__) -class UpdateInstance(forms.SelfHandlingForm): +class UpdateWinDC(forms.SelfHandlingForm): tenant_id = forms.CharField(widget=forms.HiddenInput) - instance = forms.CharField(widget=forms.HiddenInput) + data_center = forms.CharField(widget=forms.HiddenInput) name = forms.CharField(required=True) def handle(self, request, data): try: - server = api.nova.server_update(request, data['instance'], + server = api.nova.server_update(request, data['data_center'], data['name']) messages.success(request, - _('Instance "%s" updated.') % data['name']) + _('Data Center "%s" updated.') % data['name']) return server except: redirect = reverse("horizon:project:windc:index") exceptions.handle(request, - _('Unable to update instance.'), + _('Unable to update data center.'), redirect=redirect) diff --git a/dashboard/windc/panel.py b/dashboard/windc/panel.py index 92bad57..0731194 100644 --- a/dashboard/windc/panel.py +++ b/dashboard/windc/panel.py @@ -22,7 +22,7 @@ from openstack_dashboard.dashboards.project import dashboard class WinDC(horizon.Panel): - name = _("Windows Services") + name = _("Windows Data Centers") slug = 'windc' diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py index 3875f06..1c1811a 100644 --- a/dashboard/windc/tables.py +++ b/dashboard/windc/tables.py @@ -14,6 +14,10 @@ # License for the specific language governing permissions and limitations # under the License. + +# TO DO: clear extra modules + + import logging from django import shortcuts @@ -37,98 +41,32 @@ from openstack_dashboard.dashboards.project.access_and_security \ LOG = logging.getLogger(__name__) -ACTIVE_STATES = ("ACTIVE",) - -POWER_STATES = { - 0: "NO STATE", - 1: "RUNNING", - 2: "SHUTDOWN", - 3: "FAILED", - 4: "BUILDING", -} - -PAUSE = 0 -UNPAUSE = 1 -SUSPEND = 0 -RESUME = 1 - - -def is_deleting(instance): - task_state = getattr(instance, "OS-EXT-STS:task_state", None) - if not task_state: - return False - return task_state.lower() == "deleting" - - -class RebootService(tables.BatchAction): - name = "reboot" - action_present = _("Reboot") - action_past = _("Rebooted") - data_type_singular = _("Service") - data_type_plural = _("Services") - classes = ('btn-danger', 'btn-reboot') - - def allowed(self, request, instance=None): - return ((instance.status in ACTIVE_STATES - or instance.status == 'SHUTOFF') - and not is_deleting(instance)) - - def action(self, request, obj_id): - api.nova.server_reboot(request, obj_id) - class CreateService(tables.LinkAction): name = "CreateService" - verbose_name = _("Create Windows Service") + verbose_name = _("Create Service") url = "horizon:project:windc:create" classes = ("btn-launch", "ajax-modal") def allowed(self, request, datum): - try: - limits = api.nova.tenant_absolute_limits(request, reserved=True) - - instances_available = limits['maxTotalInstances'] \ - - limits['totalInstancesUsed'] - cores_available = limits['maxTotalCores'] \ - - limits['totalCoresUsed'] - ram_available = limits['maxTotalRAMSize'] - limits['totalRAMUsed'] - - if instances_available <= 0 or cores_available <= 0 \ - or ram_available <= 0: - if "disabled" not in self.classes: - self.classes = [c for c in self.classes] + ['disabled'] - self.verbose_name = string_concat(self.verbose_name, ' ', - _("(Quota exceeded)")) - else: - self.verbose_name = _("Create Windows Service") - classes = [c for c in self.classes if c != "disabled"] - self.classes = classes - except: - LOG.exception("Failed to retrieve quota information") - # If we can't get the quota information, leave it to the - # API to check when launching - - return True # The action should always be displayed - - -class DeleteService(tables.BatchAction): - name = "DeleteService" - action_present = _("DeleteService") - action_past = _("Scheduled termination of") - data_type_singular = _("Service") - data_type_plural = _("Services") - classes = ('btn-danger', 'btn-terminate') - - def allowed(self, request, instance=None): - if instance: - # FIXME(gabriel): This is true in Essex, but in FOLSOM an instance - # can be terminated in any state. We should improve this error - # handling when LP bug 1037241 is implemented. - return instance.status not in ("PAUSED", "SUSPENDED") return True def action(self, request, obj_id): - api.nova.server_delete(request, obj_id) + # FIX ME + api.windc.datacenter.create_service(request, obj_id) + +class CreateDataCenter(tables.LinkAction): + name = "CreateDataCenter" + verbose_name = _("Create Windows Data Center") + url = "horizon:project:windc:create_dc" + classes = ("btn-launch", "ajax-modal") + + def allowed(self, request, datum): + return True + + def action(self, request, obj_id): + # FIX ME + api.windc.datacenter.create(request, obj_id) class EditService(tables.LinkAction): @@ -138,7 +76,15 @@ class EditService(tables.LinkAction): classes = ("ajax-modal", "btn-edit") def allowed(self, request, instance): - return not is_deleting(instance) + return True + +class ShowDataCenterServices(tables.LinkAction): + name = "edit" + verbose_name = _("Services") + url = "horizon:project:windc:services" + + def allowed(self, request, instance): + return True class UpdateRow(tables.Row): ajax = True @@ -150,45 +96,26 @@ class UpdateRow(tables.Row): return instance -def get_ips(instance): - template_name = 'project/windc/_instance_ips.html' - context = {"instance": instance} - return template.loader.render_to_string(template_name, context) +class WinDCTable(tables.DataTable): + TASK_STATUS_CHOICES = ( + (None, True), + ("none", True) + ) + STATUS_CHOICES = ( + ("active", True), + ("shutoff", True), + ("error", False), + ) + name = tables.Column("name", + link=("horizon:project:windc:services"), + verbose_name=_("Name")) - -def get_size(instance): - if hasattr(instance, "full_flavor"): - size_string = _("%(name)s | %(RAM)s RAM | %(VCPU)s VCPU " - "| %(disk)s Disk") - vals = {'name': instance.full_flavor.name, - 'RAM': sizeformat.mbformat(instance.full_flavor.ram), - 'VCPU': instance.full_flavor.vcpus, - 'disk': sizeformat.diskgbformat(instance.full_flavor.disk)} - return size_string % vals - return _("Not available") - - -def get_power_state(instance): - return POWER_STATES.get(getattr(instance, "OS-EXT-STS:power_state", 0), '') - - -STATUS_DISPLAY_CHOICES = ( - ("resize", "Resize/Migrate"), - ("verify_resize", "Confirm or Revert Resize/Migrate"), - ("revert_resize", "Revert Resize/Migrate"), -) - - -TASK_DISPLAY_CHOICES = ( - ("image_snapshot", "Snapshotting"), - ("resize_prep", "Preparing Resize or Migrate"), - ("resize_migrating", "Resizing or Migrating"), - ("resize_migrated", "Resized or Migrated"), - ("resize_finish", "Finishing Resize or Migrate"), - ("resize_confirming", "Confirming Resize or Nigrate"), - ("resize_reverting", "Reverting Resize or Migrate"), - ("unpausing", "Resuming"), -) + class Meta: + name = "windc" + verbose_name = _("Windows Data Centers") + row_class = UpdateRow + table_actions = (CreateDataCenter,) + row_actions = (ShowDataCenterServices,) class WinServicesTable(tables.DataTable): @@ -202,29 +129,12 @@ class WinServicesTable(tables.DataTable): ("error", False), ) name = tables.Column("name", - link=("horizon:project:windc:detail"), + link=("horizon:project:windc"), verbose_name=_("Name")) - ip = tables.Column(get_ips, verbose_name=_("IP Address")) - size = tables.Column(get_size, - verbose_name=_("Type"), - attrs={'data-type': 'type'}) - status = tables.Column("status", - filters=(title, replace_underscores), - verbose_name=_("Status"), - status=True, - status_choices=STATUS_CHOICES, - display_choices=STATUS_DISPLAY_CHOICES) - task = tables.Column("OS-EXT-STS:task_state", - verbose_name=_("Task"), - filters=(title, replace_underscores), - status=True, - status_choices=TASK_STATUS_CHOICES, - display_choices=TASK_DISPLAY_CHOICES) class Meta: - name = "windc" - verbose_name = _("Windows Services") - status_columns = ["status", "task"] + name = "services" + verbose_name = _("Services") row_class = UpdateRow - table_actions = (CreateService, DeleteService) - row_actions = (EditService, RebootService) + table_actions = (CreateService,) + row_actions = (EditService,) diff --git a/dashboard/windc/templates/windc/index.html b/dashboard/windc/templates/windc/index.html index 1ab2736..fa172ab 100644 --- a/dashboard/windc/templates/windc/index.html +++ b/dashboard/windc/templates/windc/index.html @@ -1,9 +1,9 @@ {% extends 'base.html' %} {% load i18n %} -{% block title %}{% trans "Windows Services" %}{% endblock %} +{% block title %}{% trans "Windows Data Centers" %}{% endblock %} {% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Windows Services") %} + {% include "horizon/common/_page_header.html" with title=_("Windows Data Centers") %} {% endblock page_header %} {% block main %} diff --git a/dashboard/windc/urls.py b/dashboard/windc/urls.py index 8f52b6f..6654ed2 100644 --- a/dashboard/windc/urls.py +++ b/dashboard/windc/urls.py @@ -20,12 +20,15 @@ from django.conf.urls.defaults import patterns, url -from .views import IndexView, CreateWinServiceView +from .views import IndexView, CreateWinDCView, WinServices, CreateWinServiceView VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views' urlpatterns = patterns(VIEW_MOD, 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'^(?P[^/]+)/$', WinServices.as_view(), + name='services') ) diff --git a/dashboard/windc/views.py b/dashboard/windc/views.py index c5854e0..5948af1 100644 --- a/dashboard/windc/views.py +++ b/dashboard/windc/views.py @@ -15,8 +15,7 @@ # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # 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. @@ -36,51 +35,61 @@ from horizon import tables from horizon import workflows from openstack_dashboard import api -from .tables import WinServicesTable -from .workflows import CreateWinService +from .tables import WinDCTable, WinServicesTable +from .tabs import WinServicesTab +from .workflows import CreateWinService, CreateWinDC LOG = logging.getLogger(__name__) class IndexView(tables.DataTableView): - table_class = WinServicesTable + table_class = WinDCTable template_name = 'project/windc/index.html' def get_data(self): # Gather our instances try: - instances = api.nova.server_list(self.request) + data_centers = api.windc.datacenter_list(self.request) except: - instances = [] + data_centers = [] exceptions.handle(self.request, - _('Unable to retrieve instances.')) - # Gather our flavors and correlate our instances to them - if instances: - try: - flavors = api.nova.flavor_list(self.request) - except: - flavors = [] - exceptions.handle(self.request, ignore=True) + _('Unable to retrieve data centers list.')) + return data_centers - full_flavors = SortedDict([(str(flavor.id), flavor) - for flavor in flavors]) - # Loop through instances to get flavor info. - for instance in instances: - try: - flavor_id = instance.flavor["id"] - if flavor_id in full_flavors: - instance.full_flavor = full_flavors[flavor_id] - else: - # If the flavor_id is not in full_flavors list, - # get it via nova api. - instance.full_flavor = api.nova.flavor_get( - self.request, flavor_id) - except: - msg = _('Unable to retrieve instance size information.') - exceptions.handle(self.request, msg) - return instances +class WinServices(tables.DataTableView): + table_class = WinServicesTable + template_name = 'project/windc/services.html' + + def get_context_data(self, **kwargs): + context = super(WinServices, self).get_context_data(**kwargs) + context["domain_controller_name"] = self.get_data()[0].name + return context + + def get_data(self): + try: + dc_id = self.kwargs['domain_controller_id'] + domain_controller = api.windc.datacenter_get(self.request, dc_id) + except: + redirect = reverse('horizon:project:windc:index') + exceptions.handle(self.request, + _('Unable to retrieve details for ' + 'domain_controller "%s".') % dc_id, + redirect=redirect) + self._domain_controller = [domain_controller,] + return self._domain_controller + + +class CreateWinDCView(workflows.WorkflowView): + workflow_class = CreateWinDC + template_name = "project/windc/create_dc.html" + + def get_initial(self): + initial = super(CreateWinDCView, self).get_initial() + initial['project_id'] = self.request.user.tenant_id + initial['user_id'] = self.request.user.id + return initial class CreateWinServiceView(workflows.WorkflowView): workflow_class = CreateWinService diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index 385da51..7db3e78 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -61,8 +61,22 @@ class SelectProjectUserAction(workflows.Action): class SelectProjectUser(workflows.Step): action_class = SelectProjectUserAction + #contributes = ("project_id", "user_id") +class ConfigureDCAction(workflows.Action): + dc_name = forms.CharField(label=_("Data Center Name"), + required=True, + help_text=_("A name of new data center.")) + + class Meta: + name = _("Data Center") + help_text_template = ("project/windc/_data_center_help.html") + + +class ConfigureDC(workflows.Step): + action_class = ConfigureDCAction + class ConfigureWinDCAction(workflows.Action): dc_name = forms.CharField(label=_("Domain Name"), required=False, @@ -129,11 +143,11 @@ class ConfigureWinIIS(workflows.Step): class CreateWinService(workflows.Workflow): slug = "create" - name = _("Create Windows Service") + name = _("Create Service") finalize_button_name = _("Deploy") success_message = _('Deployed %(count)s named "%(name)s".') failure_message = _('Unable to deploy %(count)s named "%(name)s".') - success_url = "horizon:project:windc:index" + success_url = "horizon:project:windc:services" default_steps = (SelectProjectUser, ConfigureWinDC, ConfigureWinIIS) @@ -148,3 +162,25 @@ class CreateWinService(workflows.Workflow): # except: # exceptions.handle(request) # return False + + +class CreateWinDC(workflows.Workflow): + slug = "create" + name = _("Create Windows Data Center") + finalize_button_name = _("Deploy") + success_message = _('Deployed %(count)s named "%(name)s".') + failure_message = _('Unable to deploy %(count)s named "%(name)s".') + success_url = "horizon:project:windc" + default_steps = (SelectProjectUser, + ConfigureDC) + + ## TO DO: + ## Need to rewrite the following code: + + #def handle(self, request, context): + # try: + # api.windc.create(request,...) + # return True + # except: + # exceptions.handle(request) + # return False From 1571d0775a8bb5685eb48d618db4c71c4d7a9b21 Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Sat, 16 Feb 2013 03:39:17 -0800 Subject: [PATCH 08/21] Added new files. --- dashboard/windc/tabs.py | 38 +++++++++++++++++++ .../templates/windc/_data_center_help.html | 2 + .../windc/templates/windc/_services.html | 3 ++ .../windc/templates/windc/create_dc.html | 11 ++++++ dashboard/windc/templates/windc/services.html | 11 ++++++ .../windc/templates/windc/services_tabs.html | 15 ++++++++ dashboard/windc/templates/windc/update.html | 11 ++++++ 7 files changed, 91 insertions(+) create mode 100644 dashboard/windc/tabs.py create mode 100644 dashboard/windc/templates/windc/_data_center_help.html create mode 100644 dashboard/windc/templates/windc/_services.html create mode 100644 dashboard/windc/templates/windc/create_dc.html create mode 100644 dashboard/windc/templates/windc/services.html create mode 100644 dashboard/windc/templates/windc/services_tabs.html create mode 100644 dashboard/windc/templates/windc/update.html diff --git a/dashboard/windc/tabs.py b/dashboard/windc/tabs.py new file mode 100644 index 0000000..95b7217 --- /dev/null +++ b/dashboard/windc/tabs.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tabs + +from openstack_dashboard import api + + +class OverviewTab(tabs.Tab): + name = _("Services") + slug = "_services" + template_name = ("project/windc/_services.html") + + def get_context_data(self, request): + dc = self.tab_group.kwargs['domain_controller'] + return {"domain_controller": dc} + + +class WinServicesTab(tabs.TabGroup): + slug = "services_details" + tabs = (OverviewTab,) + sticky = True diff --git a/dashboard/windc/templates/windc/_data_center_help.html b/dashboard/windc/templates/windc/_data_center_help.html new file mode 100644 index 0000000..68ffe5a --- /dev/null +++ b/dashboard/windc/templates/windc/_data_center_help.html @@ -0,0 +1,2 @@ +{% load i18n %} +

{% blocktrans %}Data Center is an instance with different services.{% endblocktrans %}

\ No newline at end of file diff --git a/dashboard/windc/templates/windc/_services.html b/dashboard/windc/templates/windc/_services.html new file mode 100644 index 0000000..1869508 --- /dev/null +++ b/dashboard/windc/templates/windc/_services.html @@ -0,0 +1,3 @@ +{% load i18n sizeformat %} + +

{% trans "Services" %}

\ No newline at end of file diff --git a/dashboard/windc/templates/windc/create_dc.html b/dashboard/windc/templates/windc/create_dc.html new file mode 100644 index 0000000..2fc5894 --- /dev/null +++ b/dashboard/windc/templates/windc/create_dc.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Windows Data Center" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Create Windows Data Center") %} +{% endblock page_header %} + +{% block main %} + {% include 'horizon/common/_workflow.html' %} +{% endblock %} diff --git a/dashboard/windc/templates/windc/services.html b/dashboard/windc/templates/windc/services.html new file mode 100644 index 0000000..c717759 --- /dev/null +++ b/dashboard/windc/templates/windc/services.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Data Center Services" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title="Data Center "|add:domain_controller_name %} +{% endblock page_header %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/dashboard/windc/templates/windc/services_tabs.html b/dashboard/windc/templates/windc/services_tabs.html new file mode 100644 index 0000000..8630ff8 --- /dev/null +++ b/dashboard/windc/templates/windc/services_tabs.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load i18n sizeformat %} +{% block title %}{% trans "Services" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title="Domain Controller Services" %} +{% endblock page_header %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} \ No newline at end of file diff --git a/dashboard/windc/templates/windc/update.html b/dashboard/windc/templates/windc/update.html new file mode 100644 index 0000000..aba3dc9 --- /dev/null +++ b/dashboard/windc/templates/windc/update.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Instance" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update Instance") %} +{% endblock page_header %} + +{% block main %} + {% include 'project/instances/_update.html' %} +{% endblock %} From 6c51f7d983bf14d2fb956eb8576d0737ac31f0bf Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Mon, 18 Feb 2013 00:32:23 -0800 Subject: [PATCH 09/21] Added windc API client, sync repo with dev box. --- dashboard/ReadMe.txt | 6 +- dashboard/api/windc.py | 64 ++++ dashboard/windcclient/__init__.py | 0 dashboard/windcclient/common/__init__.py | 0 dashboard/windcclient/common/base.py | 137 +++++++++ dashboard/windcclient/common/client.py | 148 +++++++++ dashboard/windcclient/common/exceptions.py | 140 +++++++++ .../windcclient/common/service_catalog.py | 62 ++++ dashboard/windcclient/common/utils.py | 291 ++++++++++++++++++ dashboard/windcclient/shell.py | 285 +++++++++++++++++ dashboard/windcclient/v1/__init__.py | 0 dashboard/windcclient/v1/client.py | 27 ++ dashboard/windcclient/v1/datacenters.py | 49 +++ 13 files changed, 1207 insertions(+), 2 deletions(-) create mode 100644 dashboard/api/windc.py create mode 100644 dashboard/windcclient/__init__.py create mode 100644 dashboard/windcclient/common/__init__.py create mode 100644 dashboard/windcclient/common/base.py create mode 100644 dashboard/windcclient/common/client.py create mode 100644 dashboard/windcclient/common/exceptions.py create mode 100644 dashboard/windcclient/common/service_catalog.py create mode 100644 dashboard/windcclient/common/utils.py create mode 100644 dashboard/windcclient/shell.py create mode 100644 dashboard/windcclient/v1/__init__.py create mode 100644 dashboard/windcclient/v1/client.py create mode 100644 dashboard/windcclient/v1/datacenters.py diff --git a/dashboard/ReadMe.txt b/dashboard/ReadMe.txt index 8d5cc0d..45d191b 100644 --- a/dashboard/ReadMe.txt +++ b/dashboard/ReadMe.txt @@ -6,7 +6,9 @@ This file is described how to install new tab on horizon dashboard. We should do the following: 1. Copy directory 'windc' to directory '/opt/stack/horizon/openstack_dashboard/dashboards/project' - 2. Edit file '/opt/stack/horizon/openstack_dashboard/dashboards/project/dashboard.py' + 2. Copy api/windc.py to directory '/opt/stack/horizon/openstack_dashboard/api' + 3. Copy directory 'windcclient' to directory '/opt/stack/horizon/' + 4. Edit file '/opt/stack/horizon/openstack_dashboard/dashboards/project/dashboard.py' Add line with windc project: ... @@ -24,6 +26,6 @@ class BasePanels(horizon.PanelGroup): ... - 3. Run the test Django server: + 5. Run the test Django server: cd /opt/stack/horizon python manage.py runserver 67.207.197.36:8080 \ No newline at end of file diff --git a/dashboard/api/windc.py b/dashboard/api/windc.py new file mode 100644 index 0000000..1210ae8 --- /dev/null +++ b/dashboard/api/windc.py @@ -0,0 +1,64 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import urlparse + +from django.utils.decorators import available_attrs + +from windcclient.v1 import client as windc_client + +#from horizon.api import base + + +__all__ = ('datacenter_get','datacenter_list', + 'datacenter_create','datacenter_delete') + + +LOG = logging.getLogger(__name__) + + +def windcclient(request): + o = urlparse.urlparse("http://127.0.0.1:8082") + url = "http://127.0.0.1:8082/foo" + LOG.debug('windcclient connection created using token "%s" and url "%s"' + % (request.user.token, url)) + return windc_client.Client(endpoint=url, token=None) + +def datacenter_create(request, parameters): + name = parameters.get('name') + _type = parameters.get('type') + version = parameters.get('version') + ip = parameters.get('ip') + port = parameters.get('port') + user = parameters.get('user') + password = parameters.get('password') + return windcclient(request).datacenters.create(name, _type, + version, ip, + port, user, password) + +def datacenter_delete(request, datacenter): + return windcclient(request).datacenters.delete(datacenter) + +def datacenter_get(request, lb_id): + return windcclient(request).datacenters.get(lb_id) + +def datacenter_list(request): + return windcclient(request).datacenters.list() diff --git a/dashboard/windcclient/__init__.py b/dashboard/windcclient/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/windcclient/common/__init__.py b/dashboard/windcclient/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/windcclient/common/base.py b/dashboard/windcclient/common/base.py new file mode 100644 index 0000000..9f03504 --- /dev/null +++ b/dashboard/windcclient/common/base.py @@ -0,0 +1,137 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Base utilities to build API operation managers and objects on top of. +""" + + +def getid(obj): + """ + Abstracts the common pattern of allowing both an object or an object's ID + (UUID) as a parameter when dealing with relationships. + """ + try: + return obj.id + except AttributeError: + return obj + + +class Manager(object): + """ + Managers interact with a particular type of API and provide CRUD + operations for them. + """ + resource_class = None + + def __init__(self, api): + self.api = api + + def _list(self, url, response_key, obj_class=None, body=None): + resp, body = self.api.client.json_request('GET', url, body=body) + + if obj_class is None: + obj_class = self.resource_class + + data = body[response_key] + return [obj_class(self, res, loaded=True) for res in data if res] + + def _delete(self, url): + self.api.client.raw_request('DELETE', url) + + def _update(self, url, body, response_key=None): + resp, body = self.api.client.json_request('PUT', url, body=body) + # PUT requests may not return a body + if body: + return self.resource_class(self, body[response_key]) + + def _create(self, url, body, response_key, return_raw=False): + resp, body = self.api.client.json_request('POST', url, body=body) + if return_raw: + return body[response_key] + return self.resource_class(self, body[response_key]) + + def _get(self, url, response_key, return_raw=False): + resp, body = self.api.client.json_request('GET', url) + if return_raw: + return body[response_key] + return self.resource_class(self, body[response_key]) + + +class Resource(object): + """ + A resource represents a particular instance of an object (tenant, user, + etc). This is pretty much just a bag for attributes. + + :param manager: Manager object + :param info: dictionary representing resource attributes + :param loaded: prevent lazy-loading if set to True + """ + def __init__(self, manager, info, loaded=False): + self.manager = manager + self._info = info + self._add_details(info) + self._loaded = loaded + + def _add_details(self, info): + for (k, v) in info.iteritems(): + setattr(self, k, v) + + def __getattr__(self, k): + if k not in self.__dict__: + #NOTE(bcwaldon): disallow lazy-loading if already loaded once + if not self.is_loaded(): + self.get() + return self.__getattr__(k) + + raise AttributeError(k) + else: + return self.__dict__[k] + + def __repr__(self): + reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and + k != 'manager') + info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) + return "<%s %s>" % (self.__class__.__name__, info) + + def get_info(self): + if not self.is_loaded(): + self.get() + if self._info: + return self._info.copy() + return {} + + def get(self): + # set_loaded() first ... so if we have to bail, we know we tried. + self.set_loaded(True) + if not hasattr(self.manager, 'get'): + return + + new = self.manager.get(self.id) + if new: + self._info = new._info + self._add_details(new._info) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + if hasattr(self, 'id') and hasattr(other, 'id'): + return self.id == other.id + return self._info == other._info + + def is_loaded(self): + return self._loaded + + def set_loaded(self, val): + self._loaded = val diff --git a/dashboard/windcclient/common/client.py b/dashboard/windcclient/common/client.py new file mode 100644 index 0000000..04740cf --- /dev/null +++ b/dashboard/windcclient/common/client.py @@ -0,0 +1,148 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +""" +OpenStack Client interface. Handles the REST calls and responses. +""" + +import httplib2 +import copy +import logging +import json + +from . import exceptions +from . import utils +from .service_catalog import ServiceCatalog + + +logger = logging.getLogger(__name__) + + +class HTTPClient(httplib2.Http): + + USER_AGENT = 'python-balancerclient' + + def __init__(self, endpoint=None, token=None, username=None, + password=None, tenant_name=None, tenant_id=None, + region_name=None, auth_url=None, auth_tenant_id=None, + timeout=600, insecure=False): + super(HTTPClient, self).__init__(timeout=timeout) + self.endpoint = endpoint + self.auth_token = token + self.auth_url = auth_url + self.auth_tenant_id = auth_tenant_id + self.username = username + self.password = password + self.tenant_name = tenant_name + self.tenant_id = tenant_id + self.region_name = region_name + self.force_exception_to_status_code = True + self.disable_ssl_certificate_validation = insecure + if self.endpoint is None: + self.authenticate() + + def _http_request(self, url, method, **kwargs): + """ Send an http request with the specified characteristics. + """ + + kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) + kwargs['headers'].setdefault('User-Agent', self.USER_AGENT) + if self.auth_token: + kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) + + resp, body = super(HTTPClient, self).request(url, method, **kwargs) + + if logger.isEnabledFor(logging.DEBUG): + utils.http_log(logger, (url, method,), kwargs, resp, body) + + if resp.status in (301, 302, 305): + return self._http_request(resp['location'], method, **kwargs) + + return resp, body + + def _json_request(self, method, url, **kwargs): + """ Wrapper around _http_request to handle setting headers, + JSON enconding/decoding and error handling. + """ + + kwargs.setdefault('headers', {}) + kwargs['headers'].setdefault('Content-Type', 'application/json') + + if 'body' in kwargs and kwargs['body'] is not None: + kwargs['body'] = json.dumps(kwargs['body']) + + resp, body = self._http_request(url, method, **kwargs) + + if body: + try: + body = json.loads(body) + except ValueError: + logger.debug("Could not decode JSON from body: %s" % body) + else: + logger.debug("No body was returned.") + body = None + + if 400 <= resp.status < 600: + raise exceptions.from_response(resp, body) + + return resp, body + + def raw_request(self, method, url, **kwargs): + url = self.endpoint + url + + kwargs.setdefault('headers', {}) + kwargs['headers'].setdefault('Content-Type', + 'application/octet-stream') + + resp, body = self._http_request(url, method, **kwargs) + + if 400 <= resp.status < 600: + raise exceptions.from_response(resp, body) + + return resp, body + + def json_request(self, method, url, **kwargs): + url = self.endpoint + url + resp, body = self._json_request(method, url, **kwargs) + return resp, body + + def authenticate(self): + token_url = self.auth_url + "/tokens" + body = {'auth': {'passwordCredentials': {'username': self.username, + 'password': self.password}}} + if self.tenant_id: + body['auth']['tenantId'] = self.tenant_id + elif self.tenant_name: + body['auth']['tenantName'] = self.tenant_name + + tmp_follow_all_redirects = self.follow_all_redirects + self.follow_all_redirects = True + try: + resp, body = self._json_request('POST', token_url, body=body) + finally: + self.follow_all_redirects = tmp_follow_all_redirects + + try: + self.service_catalog = ServiceCatalog(body['access']) + token = self.service_catalog.get_token() + self.auth_token = token['id'] + self.auth_tenant_id = token['tenant_id'] + except KeyError: + logger.exception("Parse service catalog failed.") + raise exceptions.AuthorizationFailure() + + self.endpoint = self.service_catalog.url_for(attr='region', + filter_value=self.region_name) diff --git a/dashboard/windcclient/common/exceptions.py b/dashboard/windcclient/common/exceptions.py new file mode 100644 index 0000000..4d17b8d --- /dev/null +++ b/dashboard/windcclient/common/exceptions.py @@ -0,0 +1,140 @@ +# Copyright 2010 Jacob Kaplan-Moss +""" +Exception definitions. +""" + + +class UnsupportedVersion(Exception): + """Indicates that the user is trying to use an unsupported + version of the API""" + pass + + +class CommandError(Exception): + pass + + +class AuthorizationFailure(Exception): + pass + + +class NoUniqueMatch(Exception): + pass + + +class NoTokenLookupException(Exception): + """This form of authentication does not support looking up + endpoints from an existing token.""" + pass + + +class EndpointNotFound(Exception): + """Could not find Service or Region in Service Catalog.""" + pass + + +class AmbiguousEndpoints(Exception): + """Found more than one matching endpoint in Service Catalog.""" + def __init__(self, endpoints=None): + self.endpoints = endpoints + + def __str__(self): + return "AmbiguousEndpoints: %s" % repr(self.endpoints) + + +class ClientException(Exception): + """ + The base exception class for all exceptions this library raises. + """ + def __init__(self, code, message=None, details=None): + self.code = code + self.message = message or self.__class__.message + self.details = details + + def __str__(self): + return "%s (HTTP %s)" % (self.message, self.code) + + +class BadRequest(ClientException): + """ + HTTP 400 - Bad request: you sent some malformed data. + """ + http_status = 400 + message = "Bad request" + + +class Unauthorized(ClientException): + """ + HTTP 401 - Unauthorized: bad credentials. + """ + http_status = 401 + message = "Unauthorized" + + +class Forbidden(ClientException): + """ + HTTP 403 - Forbidden: your credentials don't give you access to this + resource. + """ + http_status = 403 + message = "Forbidden" + + +class NotFound(ClientException): + """ + HTTP 404 - Not found + """ + http_status = 404 + message = "Not found" + + +class OverLimit(ClientException): + """ + HTTP 413 - Over limit: you're over the API limits for this time period. + """ + http_status = 413 + message = "Over limit" + + +# NotImplemented is a python keyword. +class HTTPNotImplemented(ClientException): + """ + HTTP 501 - Not Implemented: the server does not support this operation. + """ + http_status = 501 + message = "Not Implemented" + + +# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() +# so we can do this: +# _code_map = dict((c.http_status, c) +# for c in ClientException.__subclasses__()) +# +# Instead, we have to hardcode it: +_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized, + Forbidden, NotFound, OverLimit, HTTPNotImplemented]) + + +def from_response(response, body): + """ + Return an instance of an ClientException or subclass + based on an httplib2 response. + + Usage:: + + resp, body = http.request(...) + if resp.status != 200: + raise exception_from_response(resp, body) + """ + cls = _code_map.get(response.status, ClientException) + if body: + if hasattr(body, 'keys'): + error = body[body.keys()[0]] + message = error.get('message', None) + details = error.get('details', None) + else: + message = 'n/a' + details = body + return cls(code=response.status, message=message, details=details) + else: + return cls(code=response.status) diff --git a/dashboard/windcclient/common/service_catalog.py b/dashboard/windcclient/common/service_catalog.py new file mode 100644 index 0000000..d2a91d6 --- /dev/null +++ b/dashboard/windcclient/common/service_catalog.py @@ -0,0 +1,62 @@ +# Copyright 2011 OpenStack LLC. +# Copyright 2011, Piston Cloud Computing, Inc. +# Copyright 2011 Nebula, Inc. +# +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from . import exceptions + + +class ServiceCatalog(object): + """Helper methods for dealing with a Keystone Service Catalog.""" + + def __init__(self, resource_dict): + self.catalog = resource_dict + + def get_token(self): + """Fetch token details fron service catalog""" + token = {'id': self.catalog['token']['id'], + 'expires': self.catalog['token']['expires']} + try: + token['user_id'] = self.catalog['user']['id'] + token['tenant_id'] = self.catalog['token']['tenant']['id'] + except: + # just leave the tenant and user out if it doesn't exist + pass + return token + + def url_for(self, attr=None, filter_value=None, + service_type='loadbalancer', endpoint_type='publicURL'): + """Fetch an endpoint from the service catalog. + + Fetch the specified endpoint from the service catalog for + a particular endpoint attribute. If no attribute is given, return + the first endpoint of the specified type. + + See tests for a sample service catalog. + """ + catalog = self.catalog.get('serviceCatalog', []) + + for service in catalog: + if service['type'] != service_type: + continue + + endpoints = service['endpoints'] + for endpoint in endpoints: + if not filter_value or endpoint.get(attr) == filter_value: + return endpoint[endpoint_type] + + raise exceptions.EndpointNotFound('Endpoint not found.') diff --git a/dashboard/windcclient/common/utils.py b/dashboard/windcclient/common/utils.py new file mode 100644 index 0000000..cabcba8 --- /dev/null +++ b/dashboard/windcclient/common/utils.py @@ -0,0 +1,291 @@ +import os +import re +import sys +import uuid +import logging +import prettytable + +from . import exceptions + + +def arg(*args, **kwargs): + """Decorator for CLI args.""" + def _decorator(func): + add_arg(func, *args, **kwargs) + return func + return _decorator + + +def env(*vars, **kwargs): + """ + returns the first environment variable set + if none are non-empty, defaults to '' or keyword arg default + """ + for v in vars: + value = os.environ.get(v, None) + if value: + return value + return kwargs.get('default', '') + + +def add_arg(f, *args, **kwargs): + """Bind CLI arguments to a shell.py `do_foo` function.""" + + if not hasattr(f, 'arguments'): + f.arguments = [] + + # NOTE(sirp): avoid dups that can occur when the module is shared across + # tests. + if (args, kwargs) not in f.arguments: + # Because of the sematics of decorator composition if we just append + # to the options list positional options will appear to be backwards. + f.arguments.insert(0, (args, kwargs)) + + +def add_resource_manager_extra_kwargs_hook(f, hook): + """Adds hook to bind CLI arguments to ResourceManager calls. + + The `do_foo` calls in shell.py will receive CLI args and then in turn pass + them through to the ResourceManager. Before passing through the args, the + hooks registered here will be called, giving us a chance to add extra + kwargs (taken from the command-line) to what's passed to the + ResourceManager. + """ + if not hasattr(f, 'resource_manager_kwargs_hooks'): + f.resource_manager_kwargs_hooks = [] + + names = [h.__name__ for h in f.resource_manager_kwargs_hooks] + if hook.__name__ not in names: + f.resource_manager_kwargs_hooks.append(hook) + + +def get_resource_manager_extra_kwargs(f, args, allow_conflicts=False): + """Return extra_kwargs by calling resource manager kwargs hooks.""" + hooks = getattr(f, "resource_manager_kwargs_hooks", []) + extra_kwargs = {} + for hook in hooks: + hook_name = hook.__name__ + hook_kwargs = hook(args) + + conflicting_keys = set(hook_kwargs.keys()) & set(extra_kwargs.keys()) + if conflicting_keys and not allow_conflicts: + raise Exception("Hook '%(hook_name)s' is attempting to redefine" + " attributes '%(conflicting_keys)s'" % locals()) + + extra_kwargs.update(hook_kwargs) + + return extra_kwargs + + +def unauthenticated(f): + """ + Adds 'unauthenticated' attribute to decorated function. + Usage: + @unauthenticated + def mymethod(f): + ... + """ + f.unauthenticated = True + return f + + +def isunauthenticated(f): + """ + Checks to see if the function is marked as not requiring authentication + with the @unauthenticated decorator. Returns True if decorator is + set to True, False otherwise. + """ + return getattr(f, 'unauthenticated', False) + + +def service_type(stype): + """ + Adds 'service_type' attribute to decorated function. + Usage: + @service_type('volume') + def mymethod(f): + ... + """ + def inner(f): + f.service_type = stype + return f + return inner + + +def get_service_type(f): + """ + Retrieves service type from function + """ + return getattr(f, 'service_type', None) + + +def pretty_choice_list(l): + return ', '.join("'%s'" % i for i in l) + + +def print_list(objs, fields, formatters={}, sortby_index=0): + if sortby_index == None: + sortby = None + else: + sortby = fields[sortby_index] + + pt = prettytable.PrettyTable([f for f in fields], caching=False) + pt.align = 'l' + + for o in objs: + row = [] + for field in fields: + if field in formatters: + row.append(formatters[field](o)) + else: + field_name = field.lower().replace(' ', '_') + data = getattr(o, field_name, '') + row.append(data) + pt.add_row(row) + + print pt.get_string(sortby=sortby) + + +def print_flat_list(lst, field): + pt = prettytable.PrettyTable(field) + for el in lst: + pt.add_row([el]) + print pt.get_string() + + +def print_dict(d, property="Property"): + pt = prettytable.PrettyTable([property, 'Value'], caching=False) + pt.align = 'l' + [pt.add_row(list(r)) for r in d.iteritems()] + print pt.get_string(sortby=property) + + +def find_resource(manager, name_or_id): + """Helper for the _find_* methods.""" + # first try to get entity as integer id + try: + if isinstance(name_or_id, int) or name_or_id.isdigit(): + return manager.get(int(name_or_id)) + except exceptions.NotFound: + pass + + # now try to get entity as uuid + try: + uuid.UUID(str(name_or_id)) + return manager.get(name_or_id) + except (ValueError, exceptions.NotFound): + pass + + try: + try: + return manager.find(human_id=name_or_id) + except exceptions.NotFound: + pass + + # finally try to find entity by name + try: + return manager.find(name=name_or_id) + except exceptions.NotFound: + try: + # Volumes does not have name, but display_name + return manager.find(display_name=name_or_id) + except exceptions.NotFound: + msg = "No %s with a name or ID of '%s' exists." % \ + (manager.resource_class.__name__.lower(), name_or_id) + raise exceptions.CommandError(msg) + except exceptions.NoUniqueMatch: + msg = ("Multiple %s matches found for '%s', use an ID to be more" + " specific." % (manager.resource_class.__name__.lower(), + name_or_id)) + raise exceptions.CommandError(msg) + + +def _format_servers_list_networks(server): + output = [] + for (network, addresses) in server.networks.items(): + if len(addresses) == 0: + continue + addresses_csv = ', '.join(addresses) + group = "%s=%s" % (network, addresses_csv) + output.append(group) + + return '; '.join(output) + + +class HookableMixin(object): + """Mixin so classes can register and run hooks.""" + _hooks_map = {} + + @classmethod + def add_hook(cls, hook_type, hook_func): + if hook_type not in cls._hooks_map: + cls._hooks_map[hook_type] = [] + + cls._hooks_map[hook_type].append(hook_func) + + @classmethod + def run_hooks(cls, hook_type, *args, **kwargs): + hook_funcs = cls._hooks_map.get(hook_type) or [] + for hook_func in hook_funcs: + hook_func(*args, **kwargs) + + +def safe_issubclass(*args): + """Like issubclass, but will just return False if not a class.""" + + try: + if issubclass(*args): + return True + except TypeError: + pass + + return False + + +def import_class(import_str): + """Returns a class from a string including module and class.""" + mod_str, _sep, class_str = import_str.rpartition('.') + __import__(mod_str) + return getattr(sys.modules[mod_str], class_str) + +_slugify_strip_re = re.compile(r'[^\w\s-]') +_slugify_hyphenate_re = re.compile(r'[-\s]+') + + +# http://code.activestate.com/recipes/ +# 577257-slugify-make-a-string-usable-in-a-url-or-filename/ +def slugify(value): + """ + Normalizes string, converts to lowercase, removes non-alpha characters, + and converts spaces to hyphens. + + From Django's "django/template/defaultfilters.py". + """ + import unicodedata + if not isinstance(value, unicode): + value = unicode(value) + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') + value = unicode(_slugify_strip_re.sub('', value).strip().lower()) + return _slugify_hyphenate_re.sub('-', value) + + +def http_log(logger, args, kwargs, resp, body): +# if not logger.isEnabledFor(logging.DEBUG): +# return + + string_parts = ['curl -i'] + for element in args: + if element in ('GET', 'POST'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + logger.debug("REQ: %s\n" % "".join(string_parts)) + if 'body' in kwargs and kwargs['body']: + logger.debug("REQ BODY: %s\n" % (kwargs['body'])) + logger.debug("RESP:%s\n", resp) + logger.debug("RESP BODY:%s\n", body) diff --git a/dashboard/windcclient/shell.py b/dashboard/windcclient/shell.py new file mode 100644 index 0000000..196c7a7 --- /dev/null +++ b/dashboard/windcclient/shell.py @@ -0,0 +1,285 @@ +# Copyright 2010 Jacob Kaplan-Moss +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Command-line interface to the OpenStack LBaaS API. +""" + +import argparse +import httplib2 +import os +import sys +import logging + +from balancerclient.common import exceptions as exc +from balancerclient.common import utils +from balancerclient.v1 import shell as shell_v1 + + +LOG = logging.getLogger(__name__) + + +class OpenStackBalancerShell(object): + + def get_base_parser(self): + parser = argparse.ArgumentParser( + prog='balancer', + description=__doc__.strip(), + epilog='See "balancer help COMMAND" ' + 'for help on a specific command.', + add_help=False, + formatter_class=OpenStackHelpFormatter, + ) + + # Global arguments + parser.add_argument('-h', + '--help', + action='store_true', + help=argparse.SUPPRESS) + + parser.add_argument('--debug', + default=False, + action='store_true', + help=argparse.SUPPRESS) + + parser.add_argument('--os_username', + metavar='', + default=utils.env('OS_USERNAME'), + help='Defaults to env[OS_USERNAME]') + + parser.add_argument('--os_password', + metavar='', + default=utils.env('OS_PASSWORD'), + help='Defaults to env[OS_PASSWORD]') + + parser.add_argument('--os_tenant_name', + metavar='', + default=utils.env('OS_TENANT_NAME'), + help='Defaults to env[OS_TENANT_NAME]') + + parser.add_argument('--os_tenant_id', + metavar='', + default=utils.env('OS_TENANT_ID'), + help='Defaults to env[OS_TENANT_ID]') + + parser.add_argument('--os_auth_url', + metavar='', + default=utils.env('OS_AUTH_URL'), + help='Defaults to env[OS_AUTH_URL]') + + parser.add_argument('--os_region_name', + metavar='', + default=utils.env('OS_REGION_NAME'), + help='Defaults to env[OS_REGION_NAME]') + + parser.add_argument('--os_balancer_api_version', + metavar='', + default=utils.env('OS_BALANCER_API_VERSION', + 'KEYSTONE_VERSION'), + help='Defaults to env[OS_BALANCER_API_VERSION]' + ' or 2.0') + + parser.add_argument('--token', + metavar='', + default=utils.env('SERVICE_TOKEN'), + help='Defaults to env[SERVICE_TOKEN]') + + parser.add_argument('--endpoint', + metavar='', + default=utils.env('SERVICE_ENDPOINT'), + help='Defaults to env[SERVICE_ENDPOINT]') + + return parser + + def get_subcommand_parser(self, version): + parser = self.get_base_parser() + + self.subcommands = {} + subparsers = parser.add_subparsers(metavar='') + + try: + actions_module = { + '1': shell_v1, + }[version] + except KeyError: + actions_module = shell_v1 + + self._find_actions(subparsers, actions_module) + self._find_actions(subparsers, self) + + return parser + + def _find_actions(self, subparsers, actions_module): + for attr in (a for a in dir(actions_module) if a.startswith('do_')): + # I prefer to be hypen-separated instead of underscores. + command = attr[3:].replace('_', '-') + callback = getattr(actions_module, attr) + desc = callback.__doc__ or '' + help = desc.strip().split('\n')[0] + arguments = getattr(callback, 'arguments', []) + + subparser = subparsers.add_parser( + command, + help=help, + description=desc, + add_help=False, + formatter_class=OpenStackHelpFormatter) + subparser.add_argument('-h', '--help', action='help', + help=argparse.SUPPRESS) + self.subcommands[command] = subparser + for (args, kwargs) in arguments: + subparser.add_argument(*args, **kwargs) + subparser.set_defaults(func=callback) + + def main(self, argv): + # Parse args once to find version + parser = self.get_base_parser() + (options, args) = parser.parse_known_args(argv) + + # build available subcommands based on version + api_version = options.os_balancer_api_version + subcommand_parser = self.get_subcommand_parser(api_version) + self.parser = subcommand_parser + + # Handle top-level --help/-h before attempting to parse + # a command off the command line + if not argv or options.help: + self.do_help(options) + return 0 + + # Parse args again and call whatever callback was selected + args = subcommand_parser.parse_args(argv) + + # Deal with global arguments + if args.debug: + httplib2.debuglevel = 1 + + # Short-circuit and deal with help command right away. + if args.func == self.do_help: + self.do_help(args) + return 0 + + #FIXME(usrleon): Here should be restrict for project id same as + # for username or apikey but for compatibility it is not. + + if not utils.isunauthenticated(args.func): + # if the user hasn't provided any auth data + if not (args.token or args.endpoint or args.os_username or + args.os_password or args.os_auth_url): + raise exc.CommandError('Expecting authentication method via \n' + ' either a service token, ' + '--token or env[SERVICE_TOKEN], \n' + ' or credentials, ' + '--os_username or env[OS_USERNAME].') + + # if it looks like the user wants to provide a service token + # but is missing something + if args.token or args.endpoint and not ( + args.token and args.endpoint): + if not args.token: + raise exc.CommandError( + 'Expecting a token provided via either --token or ' + 'env[SERVICE_TOKEN]') + + if not args.endpoint: + raise exc.CommandError( + 'Expecting an endpoint provided via either --endpoint ' + 'or env[SERVICE_ENDPOINT]') + + # if it looks like the user wants to provide a credentials + # but is missing something + if ((args.os_username or args.os_password or args.os_auth_url) + and not (args.os_username and args.os_password and + args.os_auth_url)): + if not args.os_username: + raise exc.CommandError( + 'Expecting a username provided via either ' + '--os_username or env[OS_USERNAME]') + + if not args.os_password: + raise exc.CommandError( + 'Expecting a password provided via either ' + '--os_password or env[OS_PASSWORD]') + + if not args.os_auth_url: + raise exc.CommandError( + 'Expecting an auth URL via either --os_auth_url or ' + 'env[OS_AUTH_URL]') + + if utils.isunauthenticated(args.func): + self.cs = shell_generic.CLIENT_CLASS(endpoint=args.os_auth_url) + else: + token = None + endpoint = None + if args.token and args.endpoint: + token = args.token + endpoint = args.endpoint + api_version = options.os_balancer_api_version + self.cs = self.get_api_class(api_version)( + username=args.os_username, + tenant_name=args.os_tenant_name, + tenant_id=args.os_tenant_id, + token=token, + endpoint=endpoint, + password=args.os_password, + auth_url=args.os_auth_url, + region_name=args.os_region_name) + + try: + args.func(self.cs, args) + except exc.Unauthorized: + raise exc.CommandError("Invalid OpenStack LBaaS credentials.") + except exc.AuthorizationFailure: + raise exc.CommandError("Unable to authorize user") + + def get_api_class(self, version): + try: + return { + "1": shell_v1.CLIENT_CLASS, + }[version] + except KeyError: + return shell_v1.CLIENT_CLASS + + @utils.arg('command', metavar='', nargs='?', + help='Display help for ') + def do_help(self, args): + """ + Display help about this program or one of its subcommands. + """ + if getattr(args, 'command', None): + if args.command in self.subcommands: + self.subcommands[args.command].print_help() + else: + raise exc.CommandError("'%s' is not a valid subcommand" % + args.command) + else: + self.parser.print_help() + + +# I'm picky about my shell help. +class OpenStackHelpFormatter(argparse.HelpFormatter): + def start_section(self, heading): + # Title-case the headings + heading = '%s%s' % (heading[0].upper(), heading[1:]) + super(OpenStackHelpFormatter, self).start_section(heading) + + +def main(): + try: + return OpenStackBalancerShell().main(sys.argv[1:]) + except Exception, err: + LOG.exception("The operation executed with an error %r." % err) + raise diff --git a/dashboard/windcclient/v1/__init__.py b/dashboard/windcclient/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dashboard/windcclient/v1/client.py b/dashboard/windcclient/v1/client.py new file mode 100644 index 0000000..635bd4a --- /dev/null +++ b/dashboard/windcclient/v1/client.py @@ -0,0 +1,27 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from windcclient.common import client +from . import datacenters + + +class Client(object): + """Client for the WinDC v1 API.""" + + def __init__(self, **kwargs): + self.client = client.HTTPClient(**kwargs) + self.datacenters = datacenters.DCManager(self) diff --git a/dashboard/windcclient/v1/datacenters.py b/dashboard/windcclient/v1/datacenters.py new file mode 100644 index 0000000..6805be0 --- /dev/null +++ b/dashboard/windcclient/v1/datacenters.py @@ -0,0 +1,49 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from windcclient.common import base + + +class DC(base.Resource): + """Represent load balancer device instance.""" + + def __repr__(self): + return "" % self._info + + +class DCManager(base.Manager): + resource_class = DC + + def list(self): + return self._list('/datacenters', 'datacenters') + + def create(self, name, type, version, ip, port, user, password, **extra): + body = {'name': name, + 'type': type, + 'version': version, + 'ip': ip, + 'port': port, + 'user': user, + 'password': password} + body.update(extra) + return self._create('/devices', body, 'device') + + def delete(self, datacenter): + self._delete("/datacenters/%s" % base.getid(datacenter)) + + def get(self, datacenter): + return self._get("/datacenters/%s" % base.getid(datacenter), 'datacenter') From 0136a6f737314587a3aff339f6cf6cd6b1022524 Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Tue, 19 Feb 2013 00:04:06 -0800 Subject: [PATCH 10/21] Added new functional to dashboard, fixed small issues. --- dashboard/api/windc.py | 8 ++--- dashboard/windc/tables.py | 22 +++++++++++- dashboard/windc/workflows.py | 46 ++++++++++++++----------- dashboard/windcclient/common/client.py | 3 ++ dashboard/windcclient/v1/datacenters.py | 7 ++-- windc/windc/db/api.py | 7 ++-- 6 files changed, 63 insertions(+), 30 deletions(-) diff --git a/dashboard/api/windc.py b/dashboard/api/windc.py index 1210ae8..147e6b9 100644 --- a/dashboard/api/windc.py +++ b/dashboard/api/windc.py @@ -54,11 +54,11 @@ def datacenter_create(request, parameters): version, ip, port, user, password) -def datacenter_delete(request, datacenter): - return windcclient(request).datacenters.delete(datacenter) +def datacenter_delete(request, datacenter_id): + return windcclient(request).datacenters.delete(datacenter_id) -def datacenter_get(request, lb_id): - return windcclient(request).datacenters.get(lb_id) +def datacenter_get(request, datacenter_id): + return windcclient(request).datacenters.get(datacenter_id) def datacenter_list(request): return windcclient(request).datacenters.list() diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py index 1c1811a..09b9c86 100644 --- a/dashboard/windc/tables.py +++ b/dashboard/windc/tables.py @@ -55,6 +55,7 @@ class CreateService(tables.LinkAction): # FIX ME api.windc.datacenter.create_service(request, obj_id) + class CreateDataCenter(tables.LinkAction): name = "CreateDataCenter" verbose_name = _("Create Windows Data Center") @@ -69,6 +70,23 @@ class CreateDataCenter(tables.LinkAction): api.windc.datacenter.create(request, obj_id) +class DeleteDataCenter(tables.BatchAction): + name = "delete" + action_present = _("Delete") + action_past = _("Delete") + data_type_singular = _("Data Center") + data_type_plural = _("Data Center") + classes = ('btn-danger', 'btn-terminate') + + def allowed(self, request, datum): + return True + + def action(self, request, datacenter_id): + # FIX ME + datacenter = api.windc.datacenter_get(request, datacenter_id) + api.windc.datacenter_delete(request, datacenter) + + class EditService(tables.LinkAction): name = "edit" verbose_name = _("Edit Service") @@ -78,6 +96,7 @@ class EditService(tables.LinkAction): def allowed(self, request, instance): return True + class ShowDataCenterServices(tables.LinkAction): name = "edit" verbose_name = _("Services") @@ -86,6 +105,7 @@ class ShowDataCenterServices(tables.LinkAction): def allowed(self, request, instance): return True + class UpdateRow(tables.Row): ajax = True @@ -115,7 +135,7 @@ class WinDCTable(tables.DataTable): verbose_name = _("Windows Data Centers") row_class = UpdateRow table_actions = (CreateDataCenter,) - row_actions = (ShowDataCenterServices,) + row_actions = (ShowDataCenterServices,DeleteDataCenter) class WinServicesTable(tables.DataTable): diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index 7db3e78..16996a7 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -29,9 +29,6 @@ from horizon import forms from horizon import workflows from openstack_dashboard import api -from openstack_dashboard.api import cinder -from openstack_dashboard.api import glance -from openstack_dashboard.usage import quotas LOG = logging.getLogger(__name__) @@ -61,13 +58,11 @@ class SelectProjectUserAction(workflows.Action): class SelectProjectUser(workflows.Step): action_class = SelectProjectUserAction - #contributes = ("project_id", "user_id") class ConfigureDCAction(workflows.Action): - dc_name = forms.CharField(label=_("Data Center Name"), - required=True, - help_text=_("A name of new data center.")) + name = forms.CharField(label=_("Data Center Name"), + required=True) class Meta: name = _("Data Center") @@ -76,6 +71,13 @@ class ConfigureDCAction(workflows.Action): class ConfigureDC(workflows.Step): action_class = ConfigureDCAction + contibutes = ("name",) + + def contribute(self, data, context): + if data: + context['name'] = data.get("name", "") + return context + class ConfigureWinDCAction(workflows.Action): dc_name = forms.CharField(label=_("Domain Name"), @@ -167,20 +169,24 @@ class CreateWinService(workflows.Workflow): class CreateWinDC(workflows.Workflow): slug = "create" name = _("Create Windows Data Center") - finalize_button_name = _("Deploy") - success_message = _('Deployed %(count)s named "%(name)s".') - failure_message = _('Unable to deploy %(count)s named "%(name)s".') + finalize_button_name = _("Create") + success_message = _('Created data center "%(name)s".') + failure_message = _('Unable to create data center "%(name)s".') success_url = "horizon:project:windc" default_steps = (SelectProjectUser, ConfigureDC) - ## TO DO: - ## Need to rewrite the following code: - - #def handle(self, request, context): - # try: - # api.windc.create(request,...) - # return True - # except: - # exceptions.handle(request) - # return False + def handle(self, request, context): + try: + # FIX ME: + context['type'] = 'datacenter' + context['version'] = '1.0' + context['ip'] = '1.1.1.1' + context['port'] = '80' + context['user'] = 'administrator' + context['password'] = 'swordfish' + datacenter = api.windc.datacenter_create(request, context) + return True + except: + exceptions.handle(request) + return False diff --git a/dashboard/windcclient/common/client.py b/dashboard/windcclient/common/client.py index 04740cf..d8c9eb7 100644 --- a/dashboard/windcclient/common/client.py +++ b/dashboard/windcclient/common/client.py @@ -96,6 +96,8 @@ class HTTPClient(httplib2.Http): body = None if 400 <= resp.status < 600: + # DELETE THIS STRING + logger.exception(url) raise exceptions.from_response(resp, body) return resp, body @@ -110,6 +112,7 @@ class HTTPClient(httplib2.Http): resp, body = self._http_request(url, method, **kwargs) if 400 <= resp.status < 600: + logger.exception(url) raise exceptions.from_response(resp, body) return resp, body diff --git a/dashboard/windcclient/v1/datacenters.py b/dashboard/windcclient/v1/datacenters.py index 6805be0..a5c1e4e 100644 --- a/dashboard/windcclient/v1/datacenters.py +++ b/dashboard/windcclient/v1/datacenters.py @@ -40,10 +40,11 @@ class DCManager(base.Manager): 'user': user, 'password': password} body.update(extra) - return self._create('/devices', body, 'device') + return self._create('/datacenters', body, 'datacenter') def delete(self, datacenter): - self._delete("/datacenters/%s" % base.getid(datacenter)) + return self._delete("/datacenters/%s" % base.getid(datacenter)) def get(self, datacenter): - return self._get("/datacenters/%s" % base.getid(datacenter), 'datacenter') + return self._get("/datacenters/%s" % base.getid(datacenter), + 'datacenter') diff --git a/windc/windc/db/api.py b/windc/windc/db/api.py index d01c3ca..08b305c 100644 --- a/windc/windc/db/api.py +++ b/windc/windc/db/api.py @@ -88,7 +88,8 @@ def datacenter_create(conf, values): def datacenter_update(conf, datacenter_id, values): session = get_session(conf) with session.begin(): - datacenter_ref = datacenter_get(conf, datacenter_id, session=session) + datacenter_ref = session.query(models.DataCenter).\ + filter_by(id=datacenter_id).first() datacenter_ref.update(values) return datacenter_ref @@ -96,8 +97,10 @@ def datacenter_update(conf, datacenter_id, values): def datacenter_destroy(conf, datacenter_id): session = get_session(conf) with session.begin(): - datacenter_ref = device_get(conf, datacenter_id, session=session) + datacenter_ref = session.query(models.DataCenter).\ + filter_by(id=datacenter_id).first() session.delete(datacenter_ref) + return datacenter_ref # Service From 160f8b6ca43e9b9b48639ef4e3c62a915185984f Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Tue, 19 Feb 2013 04:14:25 -0800 Subject: [PATCH 11/21] Added library libsqlite3-dev to virtual environment for windc client. --- windc/tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windc/tools/pip-requires b/windc/tools/pip-requires index 70f7e25..6a22d4c 100644 --- a/windc/tools/pip-requires +++ b/windc/tools/pip-requires @@ -2,7 +2,7 @@ # the Python.h headers. Make sure you install the python-dev # package to get the right headers... greenlet>=0.3.1 - +bsqlite3-dev SQLAlchemy>=0.7 anyjson eventlet>=0.9.12 From cacee34d49b2d8441aff41ac1983e1a809e70a38 Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Tue, 19 Feb 2013 04:51:56 -0800 Subject: [PATCH 12/21] Fixed issue with virtual environment SQLAlchemy library. --- windc/tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windc/tools/pip-requires b/windc/tools/pip-requires index 6a22d4c..ffe7381 100644 --- a/windc/tools/pip-requires +++ b/windc/tools/pip-requires @@ -3,7 +3,7 @@ # package to get the right headers... greenlet>=0.3.1 bsqlite3-dev -SQLAlchemy>=0.7 +SQLAlchemy<=0.7.9 anyjson eventlet>=0.9.12 PasteDeploy From 009ec0519f1413d324f4dd8f7c17c2504e5c15f2 Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Tue, 19 Feb 2013 07:33:05 -0800 Subject: [PATCH 13/21] Fixed KEERO-85. --- dashboard/windc/workflows.py | 22 +++++++++++----------- windc/tools/pip-requires | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index 16996a7..dbfc75a 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -81,8 +81,7 @@ class ConfigureDC(workflows.Step): class ConfigureWinDCAction(workflows.Action): dc_name = forms.CharField(label=_("Domain Name"), - required=False, - help_text=_("A name of new domain.")) + required=False) dc_net_name = forms.CharField(label=_("Domain NetBIOS Name"), required=False, @@ -92,8 +91,7 @@ class ConfigureWinDCAction(workflows.Action): required=True, min_value=1, max_value=100, - initial=1, - help_text=_("Domain Controllers count.")) + initial=1) adm_password = forms.CharField(widget=forms.PasswordInput, label=_("Administrator password"), @@ -119,15 +117,13 @@ class ConfigureWinDC(workflows.Step): class ConfigureWinIISAction(workflows.Action): iis_name = forms.CharField(label=_("IIS Server Name"), - required=False, - help_text=_("A name of IIS Server.")) + required=False) iis_count = forms.IntegerField(label=_("IIS Servers Count"), required=True, min_value=1, max_value=100, - initial=1, - help_text=_("IIS Servers count.")) + initial=1) iis_domain = forms.CharField(label=_("Member of the Domain"), required=False, @@ -170,12 +166,16 @@ class CreateWinDC(workflows.Workflow): slug = "create" name = _("Create Windows Data Center") finalize_button_name = _("Create") - success_message = _('Created data center "%(name)s".') - failure_message = _('Unable to create data center "%(name)s".') - success_url = "horizon:project:windc" + success_message = _('Created data center "%s".') + failure_message = _('Unable to create data center "%s".') + success_url = "horizon:project:windc:index" default_steps = (SelectProjectUser, ConfigureDC) + def format_status_message(self, message): + name = self.context.get('name', 'noname') + return message % name + def handle(self, request, context): try: # FIX ME: diff --git a/windc/tools/pip-requires b/windc/tools/pip-requires index ffe7381..d860a56 100644 --- a/windc/tools/pip-requires +++ b/windc/tools/pip-requires @@ -2,7 +2,7 @@ # the Python.h headers. Make sure you install the python-dev # package to get the right headers... greenlet>=0.3.1 -bsqlite3-dev + SQLAlchemy<=0.7.9 anyjson eventlet>=0.9.12 From 1273092b6ab5eef139065f6418b44ea02899609c Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Wed, 20 Feb 2013 08:23:06 -0800 Subject: [PATCH 14/21] Fixed small issues. --- dashboard/api/windc.py | 36 +++++++++--------- dashboard/windc/tables.py | 32 +++------------- dashboard/windc/templates/windc/services.html | 2 +- dashboard/windc/views.py | 22 +++++------ dashboard/windc/workflows.py | 34 ++++++++--------- dashboard/windcclient/v1/client.py | 2 + dashboard/windcclient/v1/datacenters.py | 10 +---- windc/windc/api/v1/datacenters.py | 11 +----- windc/windc/api/v1/router.py | 28 +++++++------- windc/windc/api/v1/services.py | 25 +++++------- windc/windc/common/wsgi.py | 2 +- windc/windc/core/api.py | 13 ++++--- windc/windc/db/api.py | 38 ++++++------------- 13 files changed, 103 insertions(+), 152 deletions(-) diff --git a/dashboard/api/windc.py b/dashboard/api/windc.py index 147e6b9..e0407eb 100644 --- a/dashboard/api/windc.py +++ b/dashboard/api/windc.py @@ -22,11 +22,8 @@ import logging import urlparse from django.utils.decorators import available_attrs - from windcclient.v1 import client as windc_client -#from horizon.api import base - __all__ = ('datacenter_get','datacenter_list', 'datacenter_create','datacenter_delete') @@ -42,23 +39,28 @@ def windcclient(request): % (request.user.token, url)) return windc_client.Client(endpoint=url, token=None) -def datacenter_create(request, parameters): - name = parameters.get('name') - _type = parameters.get('type') - version = parameters.get('version') - ip = parameters.get('ip') - port = parameters.get('port') - user = parameters.get('user') - password = parameters.get('password') - return windcclient(request).datacenters.create(name, _type, - version, ip, - port, user, password) +def datacenters_create(request, parameters): + name = parameters.get('name', '') + return windcclient(request).datacenters.create(name) -def datacenter_delete(request, datacenter_id): +def datacenters_delete(request, datacenter_id): return windcclient(request).datacenters.delete(datacenter_id) -def datacenter_get(request, datacenter_id): +def datacenters_get(request, datacenter_id): return windcclient(request).datacenters.get(datacenter_id) -def datacenter_list(request): +def datacenters_list(request): return windcclient(request).datacenters.list() + +def services_create(request, datacenter, parameters): + name = parameters.get('name', '') + return windcclient(request).services.create(datacenter, name) + +def services_list(request, datacenter): + return windcclient(request).services.list(datacenter) + +def services_get(request, datacenter, service_id): + return windcclient(request).services.get(datacenter, service_id) + +def services_delete(request, datacenter, service_id): + return windcclient(request).services.delete(datacenter, service_id) diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py index 09b9c86..8b24622 100644 --- a/dashboard/windc/tables.py +++ b/dashboard/windc/tables.py @@ -51,9 +51,9 @@ class CreateService(tables.LinkAction): def allowed(self, request, datum): return True - def action(self, request, obj_id): + def action(self, request, service): # FIX ME - api.windc.datacenter.create_service(request, obj_id) + api.windc.services_create(request, service) class CreateDataCenter(tables.LinkAction): @@ -65,9 +65,8 @@ class CreateDataCenter(tables.LinkAction): def allowed(self, request, datum): return True - def action(self, request, obj_id): - # FIX ME - api.windc.datacenter.create(request, obj_id) + def action(self, request, datacenter): + api.windc.datacenters_create(request, datacenter) class DeleteDataCenter(tables.BatchAction): @@ -82,9 +81,8 @@ class DeleteDataCenter(tables.BatchAction): return True def action(self, request, datacenter_id): - # FIX ME - datacenter = api.windc.datacenter_get(request, datacenter_id) - api.windc.datacenter_delete(request, datacenter) + datacenter = api.windc.datacenters_get(request, datacenter_id) + api.windc.datacenters_delete(request, datacenter) class EditService(tables.LinkAction): @@ -117,15 +115,6 @@ class UpdateRow(tables.Row): class WinDCTable(tables.DataTable): - TASK_STATUS_CHOICES = ( - (None, True), - ("none", True) - ) - STATUS_CHOICES = ( - ("active", True), - ("shutoff", True), - ("error", False), - ) name = tables.Column("name", link=("horizon:project:windc:services"), verbose_name=_("Name")) @@ -139,15 +128,6 @@ class WinDCTable(tables.DataTable): class WinServicesTable(tables.DataTable): - TASK_STATUS_CHOICES = ( - (None, True), - ("none", True) - ) - STATUS_CHOICES = ( - ("active", True), - ("shutoff", True), - ("error", False), - ) name = tables.Column("name", link=("horizon:project:windc"), verbose_name=_("Name")) diff --git a/dashboard/windc/templates/windc/services.html b/dashboard/windc/templates/windc/services.html index c717759..2c7af81 100644 --- a/dashboard/windc/templates/windc/services.html +++ b/dashboard/windc/templates/windc/services.html @@ -3,7 +3,7 @@ {% block title %}{% trans "Data Center Services" %}{% endblock %} {% block page_header %} - {% include "horizon/common/_page_header.html" with title="Data Center "|add:domain_controller_name %} + {% include "horizon/common/_page_header.html" with title="Data Center "|add:dc_name %} {% endblock page_header %} {% block main %} diff --git a/dashboard/windc/views.py b/dashboard/windc/views.py index 5948af1..9c34c29 100644 --- a/dashboard/windc/views.py +++ b/dashboard/windc/views.py @@ -36,7 +36,6 @@ from horizon import workflows from openstack_dashboard import api from .tables import WinDCTable, WinServicesTable -from .tabs import WinServicesTab from .workflows import CreateWinService, CreateWinDC @@ -48,9 +47,9 @@ class IndexView(tables.DataTableView): template_name = 'project/windc/index.html' def get_data(self): - # Gather our instances + # Gather our datacenters try: - data_centers = api.windc.datacenter_list(self.request) + data_centers = api.windc.datacenters_list(self.request) except: data_centers = [] exceptions.handle(self.request, @@ -64,21 +63,22 @@ class WinServices(tables.DataTableView): def get_context_data(self, **kwargs): context = super(WinServices, self).get_context_data(**kwargs) - context["domain_controller_name"] = self.get_data()[0].name + data = self.get_data() + context["dc_name"] = self.dc_name return context def get_data(self): try: dc_id = self.kwargs['domain_controller_id'] - domain_controller = api.windc.datacenter_get(self.request, dc_id) + datacenter = api.windc.datacenters_get(self.request, dc_id) + self.dc_name = datacenter.name + services = api.windc.services_list(self.request, datacenter) except: - redirect = reverse('horizon:project:windc:index') + services = [] exceptions.handle(self.request, - _('Unable to retrieve details for ' - 'domain_controller "%s".') % dc_id, - redirect=redirect) - self._domain_controller = [domain_controller,] - return self._domain_controller + _('Unable to retrieve list of services for ' + 'data center "%s".') % dc_id) + return services class CreateWinDCView(workflows.WorkflowView): diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py index dbfc75a..9724aa8 100644 --- a/dashboard/windc/workflows.py +++ b/dashboard/windc/workflows.py @@ -143,23 +143,26 @@ class CreateWinService(workflows.Workflow): slug = "create" name = _("Create Service") finalize_button_name = _("Deploy") - success_message = _('Deployed %(count)s named "%(name)s".') - failure_message = _('Unable to deploy %(count)s named "%(name)s".') + success_message = _('Created service "%s".') + failure_message = _('Unable to create service "%s".') success_url = "horizon:project:windc:services" default_steps = (SelectProjectUser, ConfigureWinDC, ConfigureWinIIS) - ## TO DO: - ## Need to rewrite the following code: + def format_status_message(self, message): + name = self.context.get('name', 'noname') + return message % name + + def handle(self, request, context): + try: + datacenter = context.get('domain_controller_name', '') + service = api.windc.services_create(request, context) + return True + except: + exceptions.handle(request) + return False - #def handle(self, request, context): - # try: - # api.windc.create(request,...) - # return True - # except: - # exceptions.handle(request) - # return False class CreateWinDC(workflows.Workflow): @@ -178,14 +181,7 @@ class CreateWinDC(workflows.Workflow): def handle(self, request, context): try: - # FIX ME: - context['type'] = 'datacenter' - context['version'] = '1.0' - context['ip'] = '1.1.1.1' - context['port'] = '80' - context['user'] = 'administrator' - context['password'] = 'swordfish' - datacenter = api.windc.datacenter_create(request, context) + datacenter = api.windc.datacenters_create(request, context) return True except: exceptions.handle(request) diff --git a/dashboard/windcclient/v1/client.py b/dashboard/windcclient/v1/client.py index 635bd4a..eaf8a2a 100644 --- a/dashboard/windcclient/v1/client.py +++ b/dashboard/windcclient/v1/client.py @@ -17,6 +17,7 @@ from windcclient.common import client from . import datacenters +from . import dcservices class Client(object): @@ -25,3 +26,4 @@ class Client(object): def __init__(self, **kwargs): self.client = client.HTTPClient(**kwargs) self.datacenters = datacenters.DCManager(self) + self.services = dcservices.DCServiceManager(self) diff --git a/dashboard/windcclient/v1/datacenters.py b/dashboard/windcclient/v1/datacenters.py index a5c1e4e..93aaef2 100644 --- a/dashboard/windcclient/v1/datacenters.py +++ b/dashboard/windcclient/v1/datacenters.py @@ -31,14 +31,8 @@ class DCManager(base.Manager): def list(self): return self._list('/datacenters', 'datacenters') - def create(self, name, type, version, ip, port, user, password, **extra): - body = {'name': name, - 'type': type, - 'version': version, - 'ip': ip, - 'port': port, - 'user': user, - 'password': password} + def create(self, name, **extra): + body = {'name': name, 'services': {}} body.update(extra) return self._create('/datacenters', body, 'datacenter') diff --git a/windc/windc/api/v1/datacenters.py b/windc/windc/api/v1/datacenters.py index 2b0e1f9..9e05062 100644 --- a/windc/windc/api/v1/datacenters.py +++ b/windc/windc/api/v1/datacenters.py @@ -26,24 +26,17 @@ from windc.db import api as db_api LOG = logging.getLogger(__name__) -class Controller(object): +class Datacenters_Controller(object): def __init__(self, conf): LOG.debug("Creating data centers controller with config:" "datacenters.py %s", conf) self.conf = conf - @utils.verify_tenant - def findLBforVM(self, req, tenant_id, vm_id): - LOG.debug("Got index request. Request: %s", req) - result = core_api.lb_find_for_vm(self.conf, tenant_id, vm_id) - return {'loadbalancers': result} - @utils.verify_tenant def index(self, req, tenant_id): LOG.debug("Got index request. Request: %s", req) result = core_api.dc_get_index(self.conf, tenant_id) LOG.debug("Got list of datacenters: %s", result) - result return {'datacenters': result} @utils.http_success_code(202) @@ -80,4 +73,4 @@ def create_resource(conf): """Datacenters resource factory method""" deserializer = wsgi.JSONRequestDeserializer() serializer = wsgi.JSONResponseSerializer() - return wsgi.Resource(Controller(conf), deserializer, serializer) \ No newline at end of file + return wsgi.Resource(Datacenters_Controller(conf), deserializer, serializer) diff --git a/windc/windc/api/v1/router.py b/windc/windc/api/v1/router.py index ed48ef1..79acc71 100644 --- a/windc/windc/api/v1/router.py +++ b/windc/windc/api/v1/router.py @@ -20,10 +20,6 @@ import routes from windc.api.v1 import datacenters from windc.api.v1 import services - -#from . import tasks - - from openstack.common import wsgi @@ -32,7 +28,7 @@ LOG = logging.getLogger(__name__) class API(wsgi.Router): - """WSGI router for balancer v1 API requests.""" + """WSGI router for windc v1 API requests.""" def __init__(self, conf, **local_conf): self.conf = conf @@ -41,16 +37,20 @@ class API(wsgi.Router): datacenter_resource = datacenters.create_resource(self.conf) datacenter_collection = tenant_mapper.collection( "datacenters", "datacenter", - controller=datacenter_resource, member_prefix="/{datacenter_id}", + controller=datacenter_resource, + member_prefix="/{datacenter_id}", formatted=False) service_resource = services.create_resource(self.conf) - service_collection = datacenter_collection.member.collection('services', 'service', - controller=service_resource, member_prefix="/{service_id}", - formatted=False) - service_collection.member.connect("/{status}", action="changeServiceStatus", - conditions={'method': ["PUT"]}) + service_collection = datacenter_collection.member.\ + collection('services','service', + controller=service_resource, + member_prefix="/{service_id}", + formatted=False) + service_collection.member.connect("/{status}", + action="changeServiceStatus", + conditions={'method': ["PUT"]}) mapper.connect("/servicetypes", - controller=datacenter_resource, - action="show_servicetypes", - conditions={'method': ["GET"]}) + controller=datacenter_resource, + action="show_servicetypes", + conditions={'method': ["GET"]}) super(API, self).__init__(mapper) diff --git a/windc/windc/api/v1/services.py b/windc/windc/api/v1/services.py index e635250..6040926 100644 --- a/windc/windc/api/v1/services.py +++ b/windc/windc/api/v1/services.py @@ -26,22 +26,17 @@ from windc.db import api as db_api LOG = logging.getLogger(__name__) -class Controller(object): +class Services_Controller(object): def __init__(self, conf): LOG.debug("Creating services controller with config:" "services.py %s", conf) self.conf = conf - @utils.verify_tenant - def findLBforVM(self, req, tenant_id, vm_id): - LOG.debug("Got index request. Request: %s", req) - result = core_api.lb_find_for_vm(self.conf, tenant_id, vm_id) - return {'loadbalancers': result} - @utils.verify_tenant def index(self, req, tenant_id, datacenter_id): LOG.debug("Got index request. Request: %s", req) - result = core_api.service_get_index(self.conf, tenant_id, datacenter_id) + result = core_api.service_get_index(self.conf, tenant_id, + datacenter_id) return {'services': result} @utils.http_success_code(202) @@ -61,19 +56,22 @@ class Controller(object): @utils.verify_tenant def delete(self, req, tenant_id, datacenter_id, service_id): LOG.debug("Got delete request. Request: %s", req) - core_api.delete_service(self.conf, tenant_id, datacenter_id, service_id) + core_api.delete_service(self.conf, tenant_id, + datacenter_id, service_id) @utils.verify_tenant def show(self, req, tenant_id, datacenter_id, service_id): LOG.debug("Got loadbalancerr info request. Request: %s", req) - result = core_api.service_get_data(self.conf, tenant_id, datacenter_id, service_id) + result = core_api.service_get_data(self.conf, tenant_id, + datacenter_id, service_id) return {'service': result} @utils.http_success_code(202) @utils.verify_tenant def update(self, req, tenant_id, datacenter_id, service_id, body): LOG.debug("Got update request. Request: %s", req) - core_api.update_service(self.conf, tenant_id, datacenter_id, service_id, body) + core_api.update_service(self.conf, tenant_id, datacenter_id, + service_id, body) return {'service': {'id': service_id}} @@ -81,7 +79,4 @@ def create_resource(conf): """Services resource factory method""" deserializer = wsgi.JSONRequestDeserializer() serializer = wsgi.JSONResponseSerializer() - return wsgi.Resource(Controller(conf), deserializer, serializer) - - - + return wsgi.Resource(Services_Controller(conf), deserializer, serializer) diff --git a/windc/windc/common/wsgi.py b/windc/windc/common/wsgi.py index 8d01d31..3f1c6b5 100644 --- a/windc/windc/common/wsgi.py +++ b/windc/windc/common/wsgi.py @@ -46,7 +46,7 @@ from windc.common import utils bind_opts = [ - cfg.StrOpt('bind_host', default='0.0.0.0'), + cfg.StrOpt('bind_host', default='localhost'), cfg.IntOpt('bind_port'), ] diff --git a/windc/windc/core/api.py b/windc/windc/core/api.py index 5c12a2b..c9d1160 100644 --- a/windc/windc/core/api.py +++ b/windc/windc/core/api.py @@ -51,19 +51,22 @@ def update_dc(conf, tenant_id, datacenter_id, body): old_dc = copy.deepcopy(dc) db_api.pack_update(dc, body) dc = db_api.datacenter_update(conf, datacenter_id, dc) - event = events.Event(events.SCOPE_DATACENTER_CHANGE, events.ACTION_MODIFY) + event = events.Event(events.SCOPE_DATACENTER_CHANGE, + events.ACTION_MODIFY) event.previous_state = old_dc events.change_event(conf, event, dc) pass def service_get_index(conf, tenant_id, datacenter_id): - srvcs = db_api.service_get_all_by_datacenter_id(conf, tenant_id, dtacenter_id) + srvcs = db_api.service_get_all_by_datacenter_id(conf, tenant_id, + datacenter_id) srv_list = [db_api.unpack_extra(srv) for srv in srvcs] return srv_list pass def create_service(conf, params): - # We need to pack all attributes which are not defined by the model explicitly + # We need to pack all attributes which are not defined + # by the model explicitly srv_params = db_api.service_pack_extra(params) srv = db_api.service_create(conf, srv_params) event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_ADD) @@ -80,7 +83,7 @@ def delete_service(conf, tenant_id, datacenter_id, service_id): pass def service_get_data(conf, tenant_id, datacenter_id, service_id): - srv = db_api.service_get(conf,service_id, tenant_id) + srv = db_api.service_get(conf, service_id, tenant_id) srv_data = db_api.unpack_extra(srv) return srv_data pass @@ -93,4 +96,4 @@ def update_service(conf, tenant_id, datacenter_id, service_id, body): event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_MODIFY) event.previous_state = old_srv events.change_event(conf, event, srv) - pass \ No newline at end of file + pass diff --git a/windc/windc/db/api.py b/windc/windc/db/api.py index 08b305c..1e06f78 100644 --- a/windc/windc/db/api.py +++ b/windc/windc/db/api.py @@ -115,31 +115,10 @@ def service_get(conf, service_id, tenant_id=None, session=None): raise exception.ServiceNotFound(service_ref=service_ref) return service_ref - -def service_get_all_by_project(conf, tenant_id): - session = get_session(conf) - query = session.query(models.Service).filter_by(tenant_id=tenant_id) - return query.all() - - -def service_get_all_by_vm_id(conf, tenant_id, vm_id): - session = get_session(conf) - query = session.query(models.Service).distinct().\ - filter_by(tenant_id=tenant_id).\ - filter(vm_id == vm_id) - return query.all() - - def service_get_all_by_datacenter_id(conf, tenant_id, datacenter_id): session = get_session(conf) query = session.query(models.Service).filter_by(datacenter_id=datacenter_id) - service_refs = query.all() - if not service_refs: - raise exception.ServiceNotFound('No service ' - 'for the datacenter %s found' - % datacenter_id) - return service_refs - + return query.all() def service_create(conf, values): session = get_session(conf) @@ -149,7 +128,6 @@ def service_create(conf, values): session.add(service_ref) return service_ref - def service_update(conf, service_id, values): session = get_session(conf) with session.begin(): @@ -158,13 +136,23 @@ def service_update(conf, service_id, values): service_ref['updated_at'] = datetime.datetime.utcnow() return service_ref - def service_destroy(conf, service_id): session = get_session(conf) with session.begin(): service_ref = service_get(conf, service_id, session=session) session.delete(service_ref) +def service_get_all_by_project(conf, tenant_id): + session = get_session(conf) + query = session.query(models.Service).filter_by(tenant_id=tenant_id) + return query.all() + +def service_get_all_by_vm_id(conf, tenant_id, vm_id): + session = get_session(conf) + query = session.query(models.Service).distinct().\ + filter_by(tenant_id=tenant_id).\ + filter(vm_id == vm_id) + return query.all() def service_count_active_by_datacenter(conf, datacenter_id): session = get_session(conf) @@ -174,5 +162,3 @@ def service_count_active_by_datacenter(conf, datacenter_id): filter_by(status=service_status.ACTIVE).\ count() return service_count - - From 8abd1ba3aa023c3da05917a33cd3ca2c590b9b21 Mon Sep 17 00:00:00 2001 From: Georgy Okrokvertskhov Date: Wed, 20 Feb 2013 11:05:46 -0800 Subject: [PATCH 15/21] Added operations for chef. They might be remove if we decide to not use chef. --- windc/windc/core/commands.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/windc/windc/core/commands.py b/windc/windc/core/commands.py index 089ec27..86473c1 100644 --- a/windc/windc/core/commands.py +++ b/windc/windc/core/commands.py @@ -17,6 +17,10 @@ TEMPLATE_DEPLOYMENT_COMMAND = "Template" CHEF_COMMAND = "Chef" +CHEF_OP_CREATE_ENV = "Env" +CHEF_OP_CREATE_ROLE = "Role" +CHEF_OP_ASSIGN_ROLE = "AssignRole" +CHEF_OP_CREATE_NODE = "CRNode" class Command: type = "Empty" @@ -25,9 +29,15 @@ class Command: def __init__(self): self.type = "Empty" self.context = None + self.data = None def __init__(self, type, context): self.type = type self.context = context + def __init__(self, type, context, data): + self.type = type + self.context = context + self.data = data + From d141fbc4dc313f1481785afce1217fcb28457c9b Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Wed, 20 Feb 2013 23:15:44 +0400 Subject: [PATCH 16/21] [KEERO-83] Windows Agent initial implementation --- WindowsAgent/Tools/NuGet.exe | Bin 0 -> 651264 bytes WindowsAgent/WindowsAgent.sln | 20 +++ WindowsAgent/WindowsAgent/App.config | 21 +++ WindowsAgent/WindowsAgent/ExecutionPlan.cs | 20 +++ WindowsAgent/WindowsAgent/MqMessage.cs | 25 ++++ WindowsAgent/WindowsAgent/PlanExecutor.cs | 139 ++++++++++++++++++ WindowsAgent/WindowsAgent/Program.cs | 78 ++++++++++ .../WindowsAgent/Properties/AssemblyInfo.cs | 36 +++++ WindowsAgent/WindowsAgent/RabbitMqClient.cs | 124 ++++++++++++++++ .../WindowsAgent/SampleExecutionPlan.json | 36 +++++ WindowsAgent/WindowsAgent/ServiceManager.cs | 111 ++++++++++++++ WindowsAgent/WindowsAgent/WindowsAgent.csproj | 92 ++++++++++++ WindowsAgent/WindowsAgent/WindowsService.cs | 95 ++++++++++++ .../WindowsAgent/WindowsServiceInstaller.cs | 39 +++++ ...le_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs | 0 ...le_5937a670-0e60-4077-877b-f7221da3dda1.cs | 0 ...le_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs | 0 WindowsAgent/WindowsAgent/packages.config | 6 + WindowsAgent/packages/repositories.config | 4 + 19 files changed, 846 insertions(+) create mode 100644 WindowsAgent/Tools/NuGet.exe create mode 100644 WindowsAgent/WindowsAgent.sln create mode 100644 WindowsAgent/WindowsAgent/App.config create mode 100644 WindowsAgent/WindowsAgent/ExecutionPlan.cs create mode 100644 WindowsAgent/WindowsAgent/MqMessage.cs create mode 100644 WindowsAgent/WindowsAgent/PlanExecutor.cs create mode 100644 WindowsAgent/WindowsAgent/Program.cs create mode 100644 WindowsAgent/WindowsAgent/Properties/AssemblyInfo.cs create mode 100644 WindowsAgent/WindowsAgent/RabbitMqClient.cs create mode 100644 WindowsAgent/WindowsAgent/SampleExecutionPlan.json create mode 100644 WindowsAgent/WindowsAgent/ServiceManager.cs create mode 100644 WindowsAgent/WindowsAgent/WindowsAgent.csproj create mode 100644 WindowsAgent/WindowsAgent/WindowsService.cs create mode 100644 WindowsAgent/WindowsAgent/WindowsServiceInstaller.cs create mode 100644 WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs create mode 100644 WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs create mode 100644 WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs create mode 100644 WindowsAgent/WindowsAgent/packages.config create mode 100644 WindowsAgent/packages/repositories.config diff --git a/WindowsAgent/Tools/NuGet.exe b/WindowsAgent/Tools/NuGet.exe new file mode 100644 index 0000000000000000000000000000000000000000..4645f4b35e8ebd8508fe5691df73ce88a4a8028a GIT binary patch literal 651264 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 z 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ℜ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 z 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 QyoYM=KXd8-r!V#Y0 + + +

+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WindowsAgent/WindowsAgent/ExecutionPlan.cs b/WindowsAgent/WindowsAgent/ExecutionPlan.cs new file mode 100644 index 0000000..9c0be86 --- /dev/null +++ b/WindowsAgent/WindowsAgent/ExecutionPlan.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mirantis.Keero.WindowsAgent +{ + class ExecutionPlan + { + public class Command + { + public string Name { get; set; } + public Dictionary Arguments { get; set; } + } + + public string[] Scripts { get; set; } + public LinkedList Commands { get; set; } + } +} diff --git a/WindowsAgent/WindowsAgent/MqMessage.cs b/WindowsAgent/WindowsAgent/MqMessage.cs new file mode 100644 index 0000000..d77ab79 --- /dev/null +++ b/WindowsAgent/WindowsAgent/MqMessage.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mirantis.Keero.WindowsAgent +{ + class MqMessage + { + private readonly Action ackFunc; + + public MqMessage(Action ackFunc) + { + this.ackFunc = ackFunc; + } + + public string Body { get; set; } + + public void Ack() + { + ackFunc(); + } + } +} diff --git a/WindowsAgent/WindowsAgent/PlanExecutor.cs b/WindowsAgent/WindowsAgent/PlanExecutor.cs new file mode 100644 index 0000000..7baab0b --- /dev/null +++ b/WindowsAgent/WindowsAgent/PlanExecutor.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Text; +using Newtonsoft.Json; + +namespace Mirantis.Keero.WindowsAgent +{ + class PlanExecutor + { + class ExecutionResult + { + public bool IsException { get; set; } + public object Result { get; set; } + } + + private readonly string path; + + public PlanExecutor(string path) + { + this.path = path; + } + + public string Execute() + { + try + { + var plan = JsonConvert.DeserializeObject(File.ReadAllText(this.path)); + var resultPath = this.path + ".result"; + List currentResults = null; + try + { + currentResults = JsonConvert.DeserializeObject>(File.ReadAllText(resultPath)); + } + catch + { + currentResults = new List(); + } + + + var runSpace = RunspaceFactory.CreateRunspace(); + runSpace.Open(); + + var runSpaceInvoker = new RunspaceInvoke(runSpace); + runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted"); + if (plan.Scripts != null) + { + foreach (var script in plan.Scripts) + { + runSpaceInvoker.Invoke(Encoding.UTF8.GetString(Convert.FromBase64String(script))); + } + } + + while (plan.Commands != null && plan.Commands.Any()) + { + var command = plan.Commands.First(); + + var pipeline = runSpace.CreatePipeline(); + var psCommand = new Command(command.Name); + if (command.Arguments != null) + { + foreach (var kvp in command.Arguments) + { + psCommand.Parameters.Add(kvp.Key, kvp.Value); + } + } + pipeline.Commands.Add(psCommand); + try + { + var result = pipeline.Invoke(); + if (result != null) + { + currentResults.Add(new ExecutionResult { + IsException = false, + Result = result.Select(SerializePsObject).Where(obj => obj != null).ToList() + }); + } + } + catch (Exception exception) + { + currentResults.Add(new ExecutionResult { + IsException = true, + Result = new[] { + exception.GetType().FullName, exception.Message + } + }); + } + finally + { + plan.Commands.RemoveFirst(); + File.WriteAllText(path, JsonConvert.SerializeObject(plan)); + File.WriteAllText(resultPath, JsonConvert.SerializeObject(currentResults)); + } + } + runSpace.Close(); + var executionResult = JsonConvert.SerializeObject(new ExecutionResult { + IsException = false, + Result = currentResults + }, Formatting.Indented); + File.Delete(resultPath); + return executionResult; + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new ExecutionResult { + IsException = true, + Result = ex.Message + }, Formatting.Indented); + } + } + + private static object SerializePsObject(PSObject obj) + { + if (obj.BaseObject is PSCustomObject) + { + var result = new Dictionary(); + foreach (var property in obj.Properties.Where(p => p.IsGettable)) + { + try + { + result[property.Name] = property.Value.ToString(); + } + catch + { + } + } + return result; + } + else + { + return obj.BaseObject; + } + } + } + +} diff --git a/WindowsAgent/WindowsAgent/Program.cs b/WindowsAgent/WindowsAgent/Program.cs new file mode 100644 index 0000000..8bb4942 --- /dev/null +++ b/WindowsAgent/WindowsAgent/Program.cs @@ -0,0 +1,78 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Threading; +using NLog; + +namespace Mirantis.Keero.WindowsAgent +{ + [DisplayName("Keero Agent")] + sealed public class Program : WindowsService + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private volatile bool stop; + private Thread thread; + private RabbitMqClient rabbitMqClient; + + static void Main(string[] args) + { + Start(new Program(), args); + } + + protected override void OnStart(string[] args) + { + base.OnStart(args); + this.rabbitMqClient = new RabbitMqClient(); + this.thread = new Thread(Loop); + this.thread.Start(); + } + + void Loop() + { + const string filePath = "data.json"; + while (!stop) + { + try + { + if (!File.Exists(filePath)) + { + var message = rabbitMqClient.GetMessage(); + File.WriteAllText(filePath, message.Body); + message.Ack(); + } + var result = new PlanExecutor(filePath).Execute(); + if(stop) break; + rabbitMqClient.SendResult(result); + File.Delete(filePath); + } + catch (Exception exception) + { + WaitOnException(exception); + } + + } + + } + + private void WaitOnException(Exception exception) + { + if (stop) return; + Log.WarnException("Exception in main loop", exception); + var i = 0; + while (!stop && i < 10) + { + Thread.Sleep(100); + i++; + } + } + + protected override void OnStop() + { + stop = true; + this.rabbitMqClient.Dispose(); + Console.WriteLine("Stop"); + base.OnStop(); + } + + } +} diff --git a/WindowsAgent/WindowsAgent/Properties/AssemblyInfo.cs b/WindowsAgent/WindowsAgent/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f7d169b --- /dev/null +++ b/WindowsAgent/WindowsAgent/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WindowsAgent")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WindowsAgent")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9591bf2c-f38b-47e0-a39d-ea9849356371")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WindowsAgent/WindowsAgent/RabbitMqClient.cs b/WindowsAgent/WindowsAgent/RabbitMqClient.cs new file mode 100644 index 0000000..18a5bc2 --- /dev/null +++ b/WindowsAgent/WindowsAgent/RabbitMqClient.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using RabbitMQ.Client; + +namespace Mirantis.Keero.WindowsAgent +{ + class RabbitMqClient : IDisposable + { + private static readonly ConnectionFactory connectionFactory; + private IConnection currentConnecton; + + static RabbitMqClient() + { + connectionFactory = new ConnectionFactory { + HostName = ConfigurationManager.AppSettings["rabbitmq.host"] ?? "localhost", + UserName = ConfigurationManager.AppSettings["rabbitmq.user"] ?? "guest", + Password = ConfigurationManager.AppSettings["rabbitmq.password"] ??"guest", + Protocol = Protocols.FromEnvironment(), + VirtualHost = ConfigurationManager.AppSettings["rabbitmq.vhost"] ?? "/", + RequestedHeartbeat = 10 + }; + } + + public RabbitMqClient() + { + + } + + public MqMessage GetMessage() + { + var queueName = ConfigurationManager.AppSettings["rabbitmq.inputQueue"] ?? Environment.MachineName.ToLower(); + try + { + IConnection connection = null; + lock (this) + { + connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection(); + } + var session = connection.CreateModel(); + session.BasicQos(0, 1, false); + session.QueueDeclare(queueName, true, false, false, null); + var consumer = new QueueingBasicConsumer(session); + var consumeTag = session.BasicConsume(queueName, false, consumer); + Console.WriteLine("Deq"); + var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs)consumer.Queue.Dequeue(); + Console.WriteLine("Message received"); + Action ackFunc = delegate { + session.BasicAck(e.DeliveryTag, false); + session.BasicCancel(consumeTag); + session.Close(); + }; + + return new MqMessage(ackFunc) { + Body = Encoding.UTF8.GetString(e.Body) + }; + } + catch (Exception) + { + + Dispose(); + throw; + } + } + + public void SendResult(string text) + { + var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? ""; + var resultQueue = ConfigurationManager.AppSettings["rabbitmq.resultQueue"] ?? "-execution-results"; + + try + { + IConnection connection = null; + lock (this) + { + connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection(); + } + var session = connection.CreateModel(); + if (!string.IsNullOrEmpty(resultQueue)) + { + session.QueueDeclare(resultQueue, true, false, false, null); + if (!string.IsNullOrEmpty(exchangeName)) + { + session.ExchangeBind(exchangeName, resultQueue, resultQueue); + } + } + var basicProperties = session.CreateBasicProperties(); + basicProperties.SetPersistent(true); + basicProperties.ContentType = "application/json"; + session.BasicPublish(exchangeName, resultQueue, basicProperties, Encoding.UTF8.GetBytes(text)); + session.Close(); + } + catch (Exception) + { + Dispose(); + throw; + } + } + + public void Dispose() + { + lock (this) + { + try + { + if (this.currentConnecton != null) + { + this.currentConnecton.Close(); + } + } + catch + { + } + finally + { + this.currentConnecton = null; + } + } + } + } +} diff --git a/WindowsAgent/WindowsAgent/SampleExecutionPlan.json b/WindowsAgent/WindowsAgent/SampleExecutionPlan.json new file mode 100644 index 0000000..333ec85 --- /dev/null +++ b/WindowsAgent/WindowsAgent/SampleExecutionPlan.json @@ -0,0 +1,36 @@ +{ + "Scripts": + [ + "ZnVuY3Rpb24gdDMgeyAxMjsgcmV0dXJuICJ0ZXN0IiB9", + "ZnVuY3Rpb24gTmV3LVBlcnNvbigpDQp7DQogIHBhcmFtICgkRmlyc3ROYW1lLCAkTGFzdE5hbWUsICRQaG9uZSkNCg0KICAkcGVyc29uID0gbmV3LW9iamVjdCBQU09iamVjdA0KDQogICRwZXJzb24gfCBhZGQtbWVtYmVyIC10eXBlIE5vdGVQcm9wZXJ0eSAtTmFtZSBGaXJzdCAtVmFsdWUgJEZpcnN0TmFtZQ0KICAkcGVyc29uIHwgYWRkLW1lbWJlciAtdHlwZSBOb3RlUHJvcGVydHkgLU5hbWUgTGFzdCAtVmFsdWUgJExhc3ROYW1lDQogICRwZXJzb24gfCBhZGQtbWVtYmVyIC10eXBlIE5vdGVQcm9wZXJ0eSAtTmFtZSBQaG9uZSAtVmFsdWUgJFBob25lDQoNCiAgcmV0dXJuICRwZXJzb24NCn0=", + "ZnVuY3Rpb24gVGVzdFRocm93KCkNCnsNCglUaHJvdyBbc3lzdGVtLkluZGV4T3V0T2ZSYW5nZUV4Y2VwdGlvbl0gDQp9" + ], + "Commands" : + [ + { + "Name": "New-Person", + "Arguments" : + { + "FirstName": "MyFirstName", + "LastName": "MyLastName", + "Phone": "123-456" + } + + }, + { + "Name": "t3", + "Arguments" : + { + } + + }, + { + "Name": "Get-Date", + + }, + { + "Name": "TestThrow", + + } + ] +} \ No newline at end of file diff --git a/WindowsAgent/WindowsAgent/ServiceManager.cs b/WindowsAgent/WindowsAgent/ServiceManager.cs new file mode 100644 index 0000000..2bcf227 --- /dev/null +++ b/WindowsAgent/WindowsAgent/ServiceManager.cs @@ -0,0 +1,111 @@ +using System; +using System.Configuration.Install; +using System.Reflection; +using System.ServiceProcess; +using NLog; + +namespace Mirantis.Keero.WindowsAgent +{ + public class ServiceManager + { + private readonly string serviceName; + + public ServiceManager(string serviceName) + { + this.serviceName = serviceName; + } + + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public bool Restart(string[] args, TimeSpan timeout) + { + var service = new ServiceController(serviceName); + try + { + var millisec1 = TimeSpan.FromMilliseconds(Environment.TickCount); + + service.Stop(); + service.WaitForStatus(ServiceControllerStatus.Stopped, timeout); + Log.Info("Service is stopped"); + + // count the rest of the timeout + var millisec2 = TimeSpan.FromMilliseconds(Environment.TickCount); + timeout = timeout - (millisec2 - millisec1); + + service.Start(args); + service.WaitForStatus(ServiceControllerStatus.Running, timeout); + Log.Info("Service has started"); + return true; + } + catch (Exception ex) + { + Log.ErrorException("Cannot restart service " + serviceName, ex); + return false; + } + } + + public bool Stop(TimeSpan timeout) + { + var service = new ServiceController(serviceName); + try + { + service.Stop(); + service.WaitForStatus(ServiceControllerStatus.Stopped, timeout); + return true; + } + catch (Exception ex) + { + Log.ErrorException("Cannot stop service " + serviceName, ex); + return false; + } + } + + public bool Start(string[] args, TimeSpan timeout) + { + var service = new ServiceController(serviceName); + try + { + service.Start(args); + service.WaitForStatus(ServiceControllerStatus.Running, timeout); + return true; + } + catch (Exception ex) + { + Log.ErrorException("Cannot start service " + serviceName, ex); + return false; + } + } + + public bool Install() + { + try + { + ManagedInstallerClass.InstallHelper( + new string[] { Assembly.GetEntryAssembly().Location }); + } + catch(Exception ex) + { + Log.ErrorException("Cannot install service " + serviceName, ex); + return false; + } + return true; + } + + public bool Uninstall() + { + try + { + ManagedInstallerClass.InstallHelper( + new string[] { "/u", Assembly.GetEntryAssembly().Location }); + } + catch (Exception ex) + { + Log.ErrorException("Cannot uninstall service " + serviceName, ex); + return false; + } + return true; + } + + } + +} diff --git a/WindowsAgent/WindowsAgent/WindowsAgent.csproj b/WindowsAgent/WindowsAgent/WindowsAgent.csproj new file mode 100644 index 0000000..60247a2 --- /dev/null +++ b/WindowsAgent/WindowsAgent/WindowsAgent.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {F7E2A8D5-6D24-4651-A4BC-1024D59F4903} + Exe + Properties + Mirantis.Keero.WindowsAgent + WindowsAgent + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll + + + ..\packages\RabbitMQ.Client.3.0.2\lib\net30\RabbitMQ.Client.dll + + + + + + + False + C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll + + + + + + + + + + + + + + Component + + + + + + Component + + + Component + + + + + + + + + + $(SolutionDir)Tools\nuget install $(ProjectDir)packages.config -o $(SolutionDir)Packages + + + \ No newline at end of file diff --git a/WindowsAgent/WindowsAgent/WindowsService.cs b/WindowsAgent/WindowsAgent/WindowsService.cs new file mode 100644 index 0000000..1ad2d0d --- /dev/null +++ b/WindowsAgent/WindowsAgent/WindowsService.cs @@ -0,0 +1,95 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.ServiceProcess; +using NLog; + +namespace Mirantis.Keero.WindowsAgent +{ + public abstract class WindowsService : ServiceBase + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + public bool RunningAsService { get; private set; } + + protected static void Start(WindowsService service, string[] arguments) + { + Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); + + if (arguments.Contains("/install", StringComparer.OrdinalIgnoreCase)) + { + new ServiceManager(service.ServiceName).Install(); + } + else if (arguments.Contains("/uninstall", StringComparer.OrdinalIgnoreCase)) + { + new ServiceManager(service.ServiceName).Uninstall(); + } + else if (arguments.Contains("/start", StringComparer.OrdinalIgnoreCase)) + { + new ServiceManager(service.ServiceName).Start(Environment.GetCommandLineArgs(), TimeSpan.FromMinutes(1)); + } + else if (arguments.Contains("/stop", StringComparer.OrdinalIgnoreCase)) + { + new ServiceManager(service.ServiceName).Stop(TimeSpan.FromMinutes(1)); + } + else if (arguments.Contains("/restart", StringComparer.OrdinalIgnoreCase)) + { + new ServiceManager(service.ServiceName).Restart(Environment.GetCommandLineArgs(), TimeSpan.FromMinutes(1)); + } + else if (!arguments.Contains("/console", StringComparer.OrdinalIgnoreCase)) + { + service.RunningAsService = true; + Run(service); + } + else + { + try + { + service.RunningAsService = false; + Console.Title = service.ServiceName; + service.OnStart(Environment.GetCommandLineArgs()); + service.WaitForExitSignal(); + } + finally + { + service.OnStop(); + service.Dispose(); + } + } + } + + protected WindowsService() + { + var displayNameAttribute = + this.GetType().GetCustomAttributes(typeof (DisplayNameAttribute), false).Cast(). + FirstOrDefault(); + if(displayNameAttribute != null) + { + ServiceName = displayNameAttribute.DisplayName; + } + } + + + protected virtual void WaitForExitSignal() + { + Console.WriteLine("Press ESC to exit"); + while (Console.ReadKey(true).Key != ConsoleKey.Escape) + { + } + } + + protected override void OnStart(string[] args) + { + Log.Info("Service {0} started", ServiceName); + + base.OnStart(args); + } + + protected override void OnStop() + { + Log.Info("Service {0} exited", ServiceName); + base.OnStop(); + } + } +} diff --git a/WindowsAgent/WindowsAgent/WindowsServiceInstaller.cs b/WindowsAgent/WindowsAgent/WindowsServiceInstaller.cs new file mode 100644 index 0000000..ca6e4c2 --- /dev/null +++ b/WindowsAgent/WindowsAgent/WindowsServiceInstaller.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; +using System.Configuration.Install; +using System.Linq; +using System.Reflection; +using System.ServiceProcess; + +namespace Mirantis.Keero.WindowsAgent +{ + [RunInstaller(true)] + public class WindowsServiceInstaller : Installer + { + public WindowsServiceInstaller() + { + var processInstaller = new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem }; + foreach (var type in Assembly.GetEntryAssembly().GetExportedTypes().Where(t => t.IsSubclassOf(typeof(ServiceBase)))) + { + var nameAttribute = type.GetCustomAttributes(typeof (DisplayNameAttribute), false) + .Cast().FirstOrDefault(); + if(nameAttribute == null) continue; + var serviceInstaller = new ServiceInstaller { + StartType = ServiceStartMode.Automatic, + ServiceName = nameAttribute.DisplayName, + DisplayName = nameAttribute.DisplayName + }; + var descriptionAttribute = type.GetCustomAttributes(typeof(DescriptionAttribute), false) + .Cast().FirstOrDefault(); + if(descriptionAttribute != null) + { + serviceInstaller.Description = descriptionAttribute.Description; + } + + Installers.Add(serviceInstaller); + } + + Installers.Add(processInstaller); + + } + } +} diff --git a/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs b/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs new file mode 100644 index 0000000..e69de29 diff --git a/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs b/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs new file mode 100644 index 0000000..e69de29 diff --git a/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs b/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs new file mode 100644 index 0000000..e69de29 diff --git a/WindowsAgent/WindowsAgent/packages.config b/WindowsAgent/WindowsAgent/packages.config new file mode 100644 index 0000000..7aabef8 --- /dev/null +++ b/WindowsAgent/WindowsAgent/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/WindowsAgent/packages/repositories.config b/WindowsAgent/packages/repositories.config new file mode 100644 index 0000000..7753eee --- /dev/null +++ b/WindowsAgent/packages/repositories.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 5873d3610b46f56a3760734fc771a2c3437ce5d9 Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Wed, 20 Feb 2013 23:39:22 +0400 Subject: [PATCH 17/21] [KEERO-83] Windows Agent: Typo fixes + sample values in config --- WindowsAgent/WindowsAgent/App.config | 9 +++++++++ WindowsAgent/WindowsAgent/RabbitMqClient.cs | 2 -- ...GeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs | 0 ...GeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs | 0 ...GeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs | 0 5 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs delete mode 100644 WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs delete mode 100644 WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs diff --git a/WindowsAgent/WindowsAgent/App.config b/WindowsAgent/WindowsAgent/App.config index 2c1d906..3df6972 100644 --- a/WindowsAgent/WindowsAgent/App.config +++ b/WindowsAgent/WindowsAgent/App.config @@ -18,4 +18,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/WindowsAgent/WindowsAgent/RabbitMqClient.cs b/WindowsAgent/WindowsAgent/RabbitMqClient.cs index 18a5bc2..0ce1a77 100644 --- a/WindowsAgent/WindowsAgent/RabbitMqClient.cs +++ b/WindowsAgent/WindowsAgent/RabbitMqClient.cs @@ -45,9 +45,7 @@ namespace Mirantis.Keero.WindowsAgent session.QueueDeclare(queueName, true, false, false, null); var consumer = new QueueingBasicConsumer(session); var consumeTag = session.BasicConsume(queueName, false, consumer); - Console.WriteLine("Deq"); var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs)consumer.Queue.Dequeue(); - Console.WriteLine("Message received"); Action ackFunc = delegate { session.BasicAck(e.DeliveryTag, false); session.BasicCancel(consumeTag); diff --git a/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs b/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs deleted file mode 100644 index e69de29..0000000 diff --git a/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs b/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs deleted file mode 100644 index e69de29..0000000 diff --git a/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs b/WindowsAgent/WindowsAgent/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs deleted file mode 100644 index e69de29..0000000 From 58d823eca55bc7566c45e2219766e106b9e59982 Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Thu, 21 Feb 2013 00:19:46 +0400 Subject: [PATCH 18/21] [KEERO-83] Windows Agent: Ability to reboot machine after execution plan is executed --- WindowsAgent/WindowsAgent/ExecutionPlan.cs | 1 + WindowsAgent/WindowsAgent/PlanExecutor.cs | 22 ++++++++++++++++--- WindowsAgent/WindowsAgent/Program.cs | 21 +++++++++++++++++- .../WindowsAgent/SampleExecutionPlan.json | 3 ++- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/WindowsAgent/WindowsAgent/ExecutionPlan.cs b/WindowsAgent/WindowsAgent/ExecutionPlan.cs index 9c0be86..899c5cc 100644 --- a/WindowsAgent/WindowsAgent/ExecutionPlan.cs +++ b/WindowsAgent/WindowsAgent/ExecutionPlan.cs @@ -16,5 +16,6 @@ namespace Mirantis.Keero.WindowsAgent public string[] Scripts { get; set; } public LinkedList Commands { get; set; } + public int RebootOnCompletion { get; set; } } } diff --git a/WindowsAgent/WindowsAgent/PlanExecutor.cs b/WindowsAgent/WindowsAgent/PlanExecutor.cs index 7baab0b..ad4910c 100644 --- a/WindowsAgent/WindowsAgent/PlanExecutor.cs +++ b/WindowsAgent/WindowsAgent/PlanExecutor.cs @@ -24,20 +24,23 @@ namespace Mirantis.Keero.WindowsAgent this.path = path; } + public bool RebootNeeded { get; set; } + public string Execute() { + RebootNeeded = false; try { var plan = JsonConvert.DeserializeObject(File.ReadAllText(this.path)); var resultPath = this.path + ".result"; - List currentResults = null; + List currentResults = null; try { - currentResults = JsonConvert.DeserializeObject>(File.ReadAllText(resultPath)); + currentResults = JsonConvert.DeserializeObject>(File.ReadAllText(resultPath)); } catch { - currentResults = new List(); + currentResults = new List(); } @@ -100,6 +103,19 @@ namespace Mirantis.Keero.WindowsAgent IsException = false, Result = currentResults }, Formatting.Indented); + + if (plan.RebootOnCompletion > 0) + { + if (plan.RebootOnCompletion == 1) + { + RebootNeeded = !currentResults.Any(t => t.IsException); + } + else + { + RebootNeeded = true; + } + } + File.Delete(resultPath); return executionResult; } diff --git a/WindowsAgent/WindowsAgent/Program.cs b/WindowsAgent/WindowsAgent/Program.cs index 8bb4942..d228f92 100644 --- a/WindowsAgent/WindowsAgent/Program.cs +++ b/WindowsAgent/WindowsAgent/Program.cs @@ -29,6 +29,7 @@ namespace Mirantis.Keero.WindowsAgent void Loop() { + var doReboot = false; const string filePath = "data.json"; while (!stop) { @@ -40,10 +41,16 @@ namespace Mirantis.Keero.WindowsAgent File.WriteAllText(filePath, message.Body); message.Ack(); } - var result = new PlanExecutor(filePath).Execute(); + var executor = new PlanExecutor(filePath); + var result = executor.Execute(); if(stop) break; rabbitMqClient.SendResult(result); File.Delete(filePath); + if (executor.RebootNeeded) + { + doReboot = true; + break; + } } catch (Exception exception) { @@ -51,6 +58,18 @@ namespace Mirantis.Keero.WindowsAgent } } + if (doReboot) + { + Console.WriteLine("Rebooting..."); + try + { + System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0"); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } } diff --git a/WindowsAgent/WindowsAgent/SampleExecutionPlan.json b/WindowsAgent/WindowsAgent/SampleExecutionPlan.json index 333ec85..9522b70 100644 --- a/WindowsAgent/WindowsAgent/SampleExecutionPlan.json +++ b/WindowsAgent/WindowsAgent/SampleExecutionPlan.json @@ -32,5 +32,6 @@ "Name": "TestThrow", } - ] + ], + "RebootOnCompletion": 0 } \ No newline at end of file From f92d2ef4e10418c0358f91fa7ee4d27ebc9c3955 Mon Sep 17 00:00:00 2001 From: Georgy Okrokvertskhov Date: Wed, 20 Feb 2013 20:26:49 -0800 Subject: [PATCH 19/21] Added services functions to client. Need to be tested. --- dashboard/api/windc.py | 4 +-- dashboard/windcclient/v1/client.py | 4 +-- dashboard/windcclient/v1/services.py | 46 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 dashboard/windcclient/v1/services.py diff --git a/dashboard/api/windc.py b/dashboard/api/windc.py index e0407eb..416ea45 100644 --- a/dashboard/api/windc.py +++ b/dashboard/api/windc.py @@ -49,8 +49,8 @@ def datacenters_delete(request, datacenter_id): def datacenters_get(request, datacenter_id): return windcclient(request).datacenters.get(datacenter_id) -def datacenters_list(request): - return windcclient(request).datacenters.list() +def datacenters_list(request, datacenter_id): + return windcclient(request).datacenters.list(datacenter_id) def services_create(request, datacenter, parameters): name = parameters.get('name', '') diff --git a/dashboard/windcclient/v1/client.py b/dashboard/windcclient/v1/client.py index eaf8a2a..3928773 100644 --- a/dashboard/windcclient/v1/client.py +++ b/dashboard/windcclient/v1/client.py @@ -17,7 +17,7 @@ from windcclient.common import client from . import datacenters -from . import dcservices +from . import services class Client(object): @@ -26,4 +26,4 @@ class Client(object): def __init__(self, **kwargs): self.client = client.HTTPClient(**kwargs) self.datacenters = datacenters.DCManager(self) - self.services = dcservices.DCServiceManager(self) + self.services = services.DCServiceManager(self) diff --git a/dashboard/windcclient/v1/services.py b/dashboard/windcclient/v1/services.py new file mode 100644 index 0000000..a0fda7f --- /dev/null +++ b/dashboard/windcclient/v1/services.py @@ -0,0 +1,46 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from windcclient.common import base + + +class Service(base.Resource): + """Represent load balancer device instance.""" + + def __repr__(self): + return "" % self._info + + +class DCServiceManager(base.Manager): + resource_class = DC + + def list(self, datacenter): + return self._list('/datacenters/%s' % base.getid(datacenter), 'datacenters') + + def create(self, datacenter, service, **extra): + body = {'name': name,} + body.update(extra) + return self._create('/datacenters/%s/services' % base.getid(datacenter), + body, 'service') + + def delete(self, datacenter, service): + return self._delete("/datacenters/%s/services/%s" % [base.getid(datacenter), + base.getid(service)]) + + def get(self, datacenter, service): + return self._get("/datacenters/%s/services/%s" % [base.getid(datacenter),base.getid(service)], + 'service') From 233ce6501b9fd29ef1b2c0320bef48ee892bfb92 Mon Sep 17 00:00:00 2001 From: Georgy Okrokvertskhov Date: Wed, 20 Feb 2013 20:27:32 -0800 Subject: [PATCH 20/21] Fixed issue with data centers --- dashboard/api/windc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/api/windc.py b/dashboard/api/windc.py index 416ea45..e0407eb 100644 --- a/dashboard/api/windc.py +++ b/dashboard/api/windc.py @@ -49,8 +49,8 @@ def datacenters_delete(request, datacenter_id): def datacenters_get(request, datacenter_id): return windcclient(request).datacenters.get(datacenter_id) -def datacenters_list(request, datacenter_id): - return windcclient(request).datacenters.list(datacenter_id) +def datacenters_list(request): + return windcclient(request).datacenters.list() def services_create(request, datacenter, parameters): name = parameters.get('name', '') From 109f393526c9733500a913b68bfffc6aa9e115f1 Mon Sep 17 00:00:00 2001 From: Timur Nurlygayanov Date: Thu, 21 Feb 2013 08:07:56 -0500 Subject: [PATCH 21/21] Fixed KEERO-89 --- dashboard/windcclient/v1/services.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/dashboard/windcclient/v1/services.py b/dashboard/windcclient/v1/services.py index a0fda7f..3216f3c 100644 --- a/dashboard/windcclient/v1/services.py +++ b/dashboard/windcclient/v1/services.py @@ -18,29 +18,31 @@ from windcclient.common import base -class Service(base.Resource): - """Represent load balancer device instance.""" - +class DCService(base.Resource): def __repr__(self): return "" % self._info class DCServiceManager(base.Manager): - resource_class = DC + resource_class = DCService def list(self, datacenter): - return self._list('/datacenters/%s' % base.getid(datacenter), 'datacenters') + return self._list('/datacenters/%s' % base.getid(datacenter), + 'services') - def create(self, datacenter, service, **extra): + def create(self, datacenter, name, **extra): body = {'name': name,} body.update(extra) - return self._create('/datacenters/%s/services' % base.getid(datacenter), - body, 'service') + return self._create('/datacenters/%s' % base.getid(datacenter), + body, 'service') def delete(self, datacenter, service): - return self._delete("/datacenters/%s/services/%s" % [base.getid(datacenter), - base.getid(service)]) + return self._delete("/datacenters/%s/%s" % \ + (base.getid(datacenter), + base.getid(service))) def get(self, datacenter, service): - return self._get("/datacenters/%s/services/%s" % [base.getid(datacenter),base.getid(service)], + return self._get("/datacenters/%s/%s" % \ + (base.getid(datacenter), + base.getid(service)), 'service')