1 /*
2  * ClusterGroupDialog.cpp - ClusterGroup view implementation
3  *
4  * Copyright (c) 2008 secunet Security Networks AG
5  * Copyright (c) 2008 Adrian-Ken Rueegsegger <rueegsegger@swiss-it.ch>
6  * Copyright (c) 2008 Reto Buerki <buerki@swiss-it.ch>
7  *
8  * This work is dual-licensed under:
9  *
10  * o The terms of the GNU General Public License as published by the Free
11  *   Software Foundation, either version 2 of the License, or (at your option)
12  *   any later version.
13  *
14  * o The terms of NetCitadel End User License Agreement
15  */
16 
17 #include "ClusterGroupDialog.h"
18 
19 #include "utils.h"
20 #include "platforms.h"
21 #include "events.h"
22 
23 #include "ObjectListViewItem.h"
24 #include "FWWindow.h"
25 #include "ProjectPanel.h"
26 #include "DialogFactory.h"
27 #include "vrrpOptionsDialog.h"
28 #include "FWCmdChange.h"
29 
30 #include "fwbuilder/Cluster.h"
31 #include "fwbuilder/StateSyncClusterGroup.h"
32 #include "fwbuilder/FailoverClusterGroup.h"
33 #include "fwbuilder/Resources.h"
34 #include "fwbuilder/Interface.h"
35 
36 #include <memory>
37 
38 #include <qpixmapcache.h>
39 #include <qmessagebox.h>
40 #include <qdialog.h>
41 #include <QCoreApplication>
42 #include <QtDebug>
43 #include <QUndoStack>
44 
45 #include <algorithm>
46 #include <iostream>
47 
48 using namespace std;
49 using namespace libfwbuilder;
50 
~ClusterGroupDialog()51 ClusterGroupDialog::~ClusterGroupDialog()
52 {
53     delete m_dialog;
54 }
55 
ClusterGroupDialog(QWidget * parent)56 ClusterGroupDialog::ClusterGroupDialog(QWidget *parent)
57     : BaseObjectDialog(parent)
58 {
59     m_dialog = new Ui::ClusterGroupDialog_q;
60     m_dialog->setupUi(this);
61     obj = NULL;
62     reload = false;
63 
64     connectSignalsOfAllWidgetsToSlotChange();
65 }
66 
loadFWObject(FWObject * o)67 void ClusterGroupDialog::loadFWObject(FWObject *o)
68 {
69     obj = o;
70     ClusterGroup *g = dynamic_cast<ClusterGroup*>(obj);
71     assert(g != NULL);
72 
73     init = true;
74 
75     // disable manage members if host OS does not support clustering.
76     // Parent is either 'Cluster' or 'Interface', call getParent() approprietly
77     FWObject *parent = obj;
78     while (parent && !Cluster::isA(parent)) parent = parent->getParent();
79     if (parent == NULL)
80     {
81         throw FWException("ClusterGroupDialog: parent is NULL!");
82     }
83     cluster = Cluster::cast(parent);
84     string host_os = cluster->getStr("host_OS");
85 
86     // Sanity check
87     // Failover type could be wrong if user changed host OS of the cluster
88     string type = obj->getStr("type");
89 
90     list<QStringPair> possible_cluster_group_types;
91     if (StateSyncClusterGroup::isA(o))
92         getStateSyncTypesForOS(host_os.c_str(), possible_cluster_group_types);
93     if (FailoverClusterGroup::isA(o))
94         getFailoverTypesForOS(host_os.c_str(), possible_cluster_group_types);
95 
96     enable_master_column = Resources::os_res[host_os]->getResourceBool(
97         "/FWBuilderResources/Target/protocols/" + type + "/needs_master");
98 
99     if (enable_master_column) m_dialog->fwMemberTree->showColumn(2);
100     else m_dialog->fwMemberTree->hideColumn(2);
101 
102     bool acceptable_failover_type = false;
103     for (list<QStringPair>::iterator it=possible_cluster_group_types.begin();
104          it!=possible_cluster_group_types.end(); ++it)
105     {
106         QString t = it->first;
107         if (t == QString(type.c_str()))
108         {
109             acceptable_failover_type = true;
110             break;
111         }
112     }
113     if (!acceptable_failover_type && possible_cluster_group_types.size())
114         obj->setStr(
115             "type", possible_cluster_group_types.front().first.toStdString());
116 
117     m_dialog->obj_name->setText(QString::fromUtf8(g->getName().c_str()));
118     m_dialog->commentKeywords->loadFWObject(o);
119     m_dialog->obj_name->setEnabled(!o->isReadOnly());
120     setDisabledPalette(m_dialog->obj_name);
121 
122     QString grp_type = obj->getStr("type").c_str();
123     m_dialog->type->clear();
124     int cp = 0;
125     for (list<QStringPair>::iterator i1=possible_cluster_group_types.begin();
126          i1!=possible_cluster_group_types.end(); i1++,cp++)
127     {
128         m_dialog->type->addItem( i1->second );
129         if ( grp_type == i1->first ) m_dialog->type->setCurrentIndex(cp);
130     }
131 
132     // init link icons, master firewall is colored
133     m_dialog->fwMemberTree->clear();
134 
135     string master_iface = g->getStr("master_iface");
136     for (FWObject::iterator it = g->begin(); it != g->end(); it++)
137     {
138         FWObject *o = FWObjectReference::getObject(*it);
139         if (Interface::isA(o))
140         {
141             if (master_iface == FWObjectDatabase::getStringId(o->getId()))
142             {
143                 addIcon(o, true);
144             }
145             else
146             {
147                 addIcon(o);
148             }
149         }
150     }
151 
152     if (!Resources::getTargetCapabilityBool(host_os, "supports_cluster"))
153     {
154         m_dialog->manageMembers->setEnabled(false);
155         m_dialog->manageMembers->setToolTip(
156             QObject::tr("Feature not supported by host OS '%1'").arg(host_os.c_str()));
157     }
158     else
159     {
160         m_dialog->manageMembers->setEnabled(true);
161         m_dialog->manageMembers->setToolTip(
162             QObject::tr("Click here to manage member firewalls of this cluster group."));
163     }
164 
165     m_dialog->fwMemberTree->resizeColumnToContents(0);
166     m_dialog->fwMemberTree->resizeColumnToContents(1);
167     m_dialog->fwMemberTree->resizeColumnToContents(2);
168     m_dialog->fwMemberTree->resizeColumnToContents(3);
169 
170     QString dlgname = DialogFactory::getClusterGroupOptionsDialogName(
171         ClusterGroup::cast(obj)->getOptionsObject());
172 
173     if (fwbdebug)
174         qDebug() << "ClusterGroupDialog::loadFWObject dlgname=" << dlgname;
175 
176     m_dialog->editParameters->setEnabled(!dlgname.isEmpty());
177 
178     init = false;
179 }
180 
saveGroupType(FWObject * group)181 void ClusterGroupDialog::saveGroupType(FWObject *group)
182 {
183     QString host_os = cluster->getStr("host_OS").c_str();
184     list<QStringPair> possible_cluster_group_types;
185     if (StateSyncClusterGroup::isA(obj))
186         getStateSyncTypesForOS(host_os, possible_cluster_group_types);
187     if (FailoverClusterGroup::isA(obj))
188         getFailoverTypesForOS(host_os, possible_cluster_group_types);
189 
190     QString  grp_type = m_dialog->type->currentText();
191     list<QStringPair>::iterator li =
192         std::find_if(possible_cluster_group_types.begin(),
193                      possible_cluster_group_types.end(),
194                      findSecondInQStringPair(grp_type));
195     if (li != possible_cluster_group_types.end())
196         group->setStr("type", li->first.toLatin1().constData() );
197 }
198 
addIcon(FWObject * o,bool master)199 void ClusterGroupDialog::addIcon(FWObject *o, bool master)
200 {
201     FWObject *iface = o;
202     assert(Interface::cast(iface)!=NULL);
203     FWObject *fw = Host::getParentHost(iface);
204 //    FWObject *fw = Interface::cast(iface)->getParentHost(); // because iface can be subinterface
205     bool valid = cluster->validateMember(Firewall::cast(fw));
206     QString iface_name = QString::fromUtf8(iface->getName().c_str());
207     QString fw_name = QString::fromUtf8(fw->getName().c_str());
208 
209     QString iface_icn_file = (":/Icons/" + iface->getTypeName() +
210                               "/icon-ref").c_str();
211     QString fw_icn_file = (":/Icons/" + fw->getTypeName() +
212                            "/icon-ref").c_str();
213 
214     QPixmap iface_pm;
215     if (!QPixmapCache::find(iface_icn_file, iface_pm))
216     {
217         iface_pm.load(iface_icn_file);
218         QPixmapCache::insert(iface_icn_file, iface_pm);
219     }
220     QPixmap fw_pm;
221     if (!QPixmapCache::find(fw_icn_file, fw_pm))
222     {
223         fw_pm.load(fw_icn_file);
224         QPixmapCache::insert(fw_icn_file, fw_pm);
225     }
226 
227     ObjectListViewItem *item = new ObjectListViewItem(m_dialog->fwMemberTree);
228     int col = 0;
229 
230     item->setText(col, fw_name);
231     item->setIcon(col, QIcon(fw_pm));
232     col++;
233 
234     item->setText(col, iface_name);
235     item->setIcon(col, QIcon(iface_pm));
236     col++;
237 
238     // note that if enable_master_column==false, this column is hidden
239     // but we still need to create an item in this column.
240     if (master)  item->setText(col, tr("Master"));
241     else item->setText(col, tr(""));
242     col++;
243 
244     if (valid)
245     {
246         item->setText(col, "OK");
247         item->setToolTip(
248             col, tr("Firewall %1 can be used as a member of this cluster").arg(fw->getName().c_str()));
249     } else
250     {
251         item->setText(col, tr("Invalid"));
252         item->setToolTip(
253             col, tr("Firewall %1 can not be used as a member of this cluster\n because its host OS or platform does not match those of the cluster.").arg(fw->getName().c_str()));
254         item->setBackgroundColor(col, QColor(255, 0, 0, 100));
255     }
256 
257     item->setProperty("type", iface->getTypeName().c_str());
258     item->setFWObject(iface);
259 }
260 
changed()261 void ClusterGroupDialog::changed()
262 {
263     if (fwbdebug)
264         qDebug() << "ClusterGroupDialog::changed()";
265     if (!reload) BaseObjectDialog::changed();
266 }
267 
validate(bool * res)268 void ClusterGroupDialog::validate(bool *res)
269 {
270     *res = true;
271     if (!validateName(this, obj, m_dialog->obj_name->text()))
272     {
273         *res = false;
274     }
275 }
276 
applyChanges()277 void ClusterGroupDialog::applyChanges()
278 {
279     std::auto_ptr<FWCmdChange> cmd( new FWCmdChange(m_project, obj));
280     FWObject* new_state = cmd->getNewState();
281 
282     ClusterGroup *g = dynamic_cast<ClusterGroup*>(new_state);
283     assert(g != NULL);
284 
285     QString oldname = obj->getName().c_str();
286     new_state->setName(string(m_dialog->obj_name->text().toUtf8().constData()));
287     m_dialog->commentKeywords->applyChanges(new_state);
288 
289     saveGroupType(new_state);
290 
291     if (!cmd->getOldState()->cmp(new_state, true))
292     {
293         if (obj->isReadOnly()) return;
294         m_project->undoStack->push(cmd.release());
295     }
296 }
297 
298 /*
299  * This method is connected to the "Edit members" button and opens dialog
300  * where user chooses cluster member firewalls and interfaces
301  */
openClusterConfDialog()302 void ClusterGroupDialog::openClusterConfDialog()
303 {
304     try
305     {
306         QWidget *w = DialogFactory::createClusterConfDialog(this, obj);
307         if (w == NULL)
308         {
309             return;   // some dialogs may not be implemented yet
310         }
311         QDialog *d = dynamic_cast<QDialog*>(w);
312         assert(d != NULL);
313 
314         // connect obj changed signal
315         //connect(d, SIGNAL(membersChanged()), this, SLOT(objectChanged()));
316 
317         if (d->exec() == QDialog::Accepted)
318         {
319             // modal dialog, dialog saves data into the object
320 
321             // update object tree (if members have changed, the object
322             // properties summary text may have to change too)
323             mw->activeProject()->updateObjectInTree(obj, true);
324 
325             // reload object to reflect changes in members
326             loadFWObject(obj);
327 
328             // mark as modified
329             changed();
330         }
331         delete d;
332     }
333     catch (FWException &ex)
334     {
335         QMessageBox::critical(
336             this, "Firewall Builder",
337             tr("FWBuilder API error: %1").arg(ex.toString().c_str()),
338             tr("&Continue"), QString::null, QString::null, 0, 1);
339         return;
340     }
341 }
342 
openObject(QTreeWidgetItem * item)343 void ClusterGroupDialog::openObject(QTreeWidgetItem *item)
344 {
345     ObjectListViewItem *otvi = dynamic_cast<ObjectListViewItem*>(item);
346     assert(otvi != NULL);
347 
348     FWObject *o = otvi->getFWObject();
349     if (o != NULL)
350     {
351         QCoreApplication::postEvent(
352             mw, new showObjectInTreeEvent(o->getRoot()->getFileName().c_str(),
353                                           o->getId()));
354     }
355 }
356 
objectChanged()357 void ClusterGroupDialog::objectChanged()
358 {
359     reload = true;
360     loadFWObject(obj);
361     reload = false;
362 }
363 
364 /*
365  * this method is connected to the "Edit protocol parameters" button
366  * and opens dialog where user edits state sync and failover
367  * (heartbeat/openais/vrrp/carp/conntrack/etc) protocol parameters.
368  */
openParametersEditor()369 void ClusterGroupDialog::openParametersEditor()
370 {
371     FWOptions *gr_opt = ClusterGroup::cast(obj)->getOptionsObject();
372 
373     QDialog *dlg = dynamic_cast<QDialog*>(
374         DialogFactory::createClusterGroupOptionsDialog(this, gr_opt));
375 
376     if (dlg)
377     {
378         if (dlg->exec() == QDialog::Accepted)
379         {
380             // modal dialog, dialog saves data into the object
381 
382             // update object tree (if protocol type has changed, the
383             // object properties summary text may have to change too)
384             mw->activeProject()->updateObjectInTree(obj, true);
385             changed();
386         }
387         delete dlg;
388     }
389 }
390 
391