805e9c1f3340bcf1117f20a7d5d1a6208d5e086a
[platform/core/security/vasum.git] / server / container-admin.cpp
1 /*
2  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Contact: Jan Olszak <j.olszak@samsung.com>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License
17  */
18
19 /**
20  * @file
21  * @author  Jan Olszak (j.olszak@samsung.com)
22  * @brief   Implementation of class for administrating one container
23  */
24
25 #include "config.hpp"
26
27 #include "container-admin.hpp"
28 #include "exception.hpp"
29
30 #include "libvirt/helpers.hpp"
31 #include "logger/logger.hpp"
32 #include "utils/fs.hpp"
33 #include "utils/latch.hpp"
34 #include "utils/callback-wrapper.hpp"
35
36 #include <cassert>
37 #include <cstring>
38 #include <string>
39 #include <memory>
40 #include <cstdint>
41
42
43 namespace security_containers {
44
45 namespace {
46
47 // TODO: this should be in container's configuration file
48 const int SHUTDOWN_WAIT = 10 * 1000;
49
50 std::string getDomainName(virDomainPtr dom)
51 {
52     assert(dom);
53
54     const char* name = virDomainGetName(dom);
55     if (name == nullptr) {
56         LOGE("Failed to get the domain's id:\n"
57              << libvirt::libvirtFormatError());
58         throw ContainerOperationException();
59     }
60
61     return name;
62 }
63
64 } // namespace
65
66 const std::uint64_t DEFAULT_CPU_SHARES = 1024;
67 const std::uint64_t DEFAULT_VCPU_PERIOD_MS = 100000;
68
69 ContainerAdmin::ContainerAdmin(const ContainerConfig& config)
70     : mConfig(config),
71       mDom(utils::readFileContent(mConfig.config)),
72       mId(getDomainName(mDom.get())),
73       mDetachOnExit(false),
74       mLifecycleCallbackId(-1),
75       mRebootCallbackId(-1),
76       mNextIdForListener(1)
77 {
78     LOGD(mId << ": Instantiating ContainerAdmin object");
79
80     // ContainerAdmin owns those callbacks
81     mLifecycleCallbackId = virConnectDomainEventRegisterAny(virDomainGetConnect(mDom.get()),
82                                                             mDom.get(),
83                                                             VIR_DOMAIN_EVENT_ID_LIFECYCLE,
84                                                             VIR_DOMAIN_EVENT_CALLBACK(&ContainerAdmin::libvirtLifecycleCallback),
85                                                             utils::createCallbackWrapper(this, mLibvirtGuard.spawn()),
86                                                             &utils::deleteCallbackWrapper<ContainerAdmin*>);
87
88     if (mLifecycleCallbackId < 0) {
89         LOGE(mId << ": Failed to register a libvirt lifecycle callback");
90         throw ContainerOperationException(mId + ": Failed to register a libvirt lifecycle callback");
91     }
92
93     mRebootCallbackId = virConnectDomainEventRegisterAny(virDomainGetConnect(mDom.get()),
94                                                          mDom.get(),
95                                                          VIR_DOMAIN_EVENT_ID_REBOOT,
96                                                          VIR_DOMAIN_EVENT_CALLBACK(&ContainerAdmin::libvirtRebootCallback),
97                                                          utils::createCallbackWrapper(this, mLibvirtGuard.spawn()),
98                                                          &utils::deleteCallbackWrapper<ContainerAdmin*>);
99
100     if (mRebootCallbackId < 0) {
101         LOGE(mId << ": Failed to register a libvirt reboot callback");
102         virConnectDomainEventDeregisterAny(virDomainGetConnect(mDom.get()),
103                                            mLifecycleCallbackId);
104         throw ContainerOperationException(mId + ": Failed to register a libvirt reboot callback");
105     }
106 }
107
108
109 ContainerAdmin::~ContainerAdmin()
110 {
111     LOGD(mId << ": Destroying ContainerAdmin object...");
112
113     // Deregister callbacks
114     if (mLifecycleCallbackId >= 0) {
115         virConnectDomainEventDeregisterAny(virDomainGetConnect(mDom.get()),
116                                            mLifecycleCallbackId);
117     }
118     if (mRebootCallbackId >= 0) {
119         virConnectDomainEventDeregisterAny(virDomainGetConnect(mDom.get()),
120                                            mRebootCallbackId);
121     }
122
123     // Try to forcefully stop
124     if (!mDetachOnExit) {
125         try {
126             destroy();
127         } catch (ServerException&) {
128             LOGE(mId << ": Failed to destroy the container");
129         }
130     }
131
132     LOGD(mId << ": ContainerAdmin object destroyed");
133 }
134
135
136 const std::string& ContainerAdmin::getId() const
137 {
138     return mId;
139 }
140
141
142 void ContainerAdmin::start()
143 {
144     assert(mDom);
145
146     LOGD(mId << ": Starting...");
147     if (isRunning()) {
148         LOGD(mId << ": Already running - nothing to do...");
149         return;
150     }
151
152     // In order to update daemon without shutting down the containers
153     // autodestroy option must NOT be set. It's best to create domain
154     // without any flags.
155     u_int flags = VIR_DOMAIN_NONE;
156
157     if (virDomainCreateWithFlags(mDom.get(), flags) < 0) {
158         LOGE(mId << ": Failed to start the container\n"
159              << libvirt::libvirtFormatError());
160         throw ContainerOperationException();
161     }
162
163     LOGD(mId << ": Started");
164 }
165
166
167 void ContainerAdmin::stop()
168 {
169     assert(mDom);
170
171     LOGD(mId << ": Stopping procedure started...");
172     if (isStopped()) {
173         LOGD(mId << ": Already crashed/down/off - nothing to do");
174         return;
175     }
176
177     utils::Latch stoppedOccured;
178
179     LifecycleListener setStopped = [&](const int eventId, const int detailId) {
180         if (eventId == VIR_DOMAIN_EVENT_STOPPED) {
181             if (detailId != VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN) {
182                 LOGW(mId << ": shutdown requested, but the container stopped with a different status: "
183                      << libvirt::libvirtEventDetailToString(eventId, detailId));
184             }
185             stoppedOccured.set();
186         }
187     };
188
189     ListenerId id = registerLifecycleListener(setStopped, nullptr);
190     shutdown();
191     bool stopped = stoppedOccured.wait(SHUTDOWN_WAIT);
192     removeListener(id);
193
194     if (!stopped) {
195         LOGW(mId << ": Gracefull shutdown timed out, the container is still running, destroying");
196         destroy();
197     }
198
199     LOGD(mId << ": Stopping procedure ended");
200 }
201
202
203 void ContainerAdmin::destroy()
204 {
205     assert(mDom);
206
207     LOGD(mId << ": Destroying...");
208     if (isStopped()) {
209         LOGD(mId << ": Already crashed/down/off - nothing to do");
210         return;
211     }
212
213     setSchedulerLevel(SchedulerLevel::FOREGROUND);
214
215     // Forceful termination of the guest
216     u_int flags = VIR_DOMAIN_DESTROY_DEFAULT;
217
218     if (virDomainDestroyFlags(mDom.get(), flags) < 0) {
219         LOGE(mId << ": Error while destroying the container:\n"
220              << libvirt::libvirtFormatError());
221         throw ContainerOperationException();
222     }
223
224     LOGD(mId << ": Destroyed");
225 }
226
227
228 void ContainerAdmin::shutdown()
229 {
230     assert(mDom);
231
232     LOGD(mId << ": Shutting down...");
233     if (isStopped()) {
234         LOGD(mId << ": Already crashed/down/off - nothing to do");
235         return;
236     }
237
238     setSchedulerLevel(SchedulerLevel::FOREGROUND);
239
240     if (virDomainShutdownFlags(mDom.get(), VIR_DOMAIN_SHUTDOWN_SIGNAL) < 0) {
241         LOGE(mId << ": Error while shutting down the container:\n"
242              << libvirt::libvirtFormatError());
243         throw ContainerOperationException();
244     }
245
246     LOGD(mId << ": Shut down initiated (async)");
247 }
248
249
250 bool ContainerAdmin::isRunning()
251 {
252     return getState() == VIR_DOMAIN_RUNNING;
253 }
254
255
256 bool ContainerAdmin::isStopped()
257 {
258     int state = getState();
259     return state == VIR_DOMAIN_SHUTDOWN ||
260            state == VIR_DOMAIN_SHUTOFF ||
261            state == VIR_DOMAIN_CRASHED;
262 }
263
264
265 void ContainerAdmin::suspend()
266 {
267     assert(mDom);
268
269     LOGD(mId << ": Pausing...");
270     if (isPaused()) {
271         LOGD(mId << ": Already paused - nothing to do...");
272         return;
273     }
274
275     if (virDomainSuspend(mDom.get()) < 0) {
276         LOGE(mId << ": Error while suspending the container:\n"
277              << libvirt::libvirtFormatError());
278         throw ContainerOperationException();
279     }
280
281     LOGD(mId << ": Paused");
282 }
283
284
285 void ContainerAdmin::resume()
286 {
287     assert(mDom);
288
289     LOGD(mId << ": Resuming...");
290     if (!isPaused()) {
291         LOGD(mId << ": Is not paused - nothing to do...");
292         return;
293     }
294
295     if (virDomainResume(mDom.get()) < 0) {
296         LOGE(mId << ": Error while resuming the container:\n"
297              << libvirt::libvirtFormatError());
298         throw ContainerOperationException();
299     }
300
301     LOGD(mId << ": Resumed");
302 }
303
304
305 bool ContainerAdmin::isPaused()
306 {
307     return getState() == VIR_DOMAIN_PAUSED;
308 }
309
310
311 int ContainerAdmin::getState()
312 {
313     assert(mDom);
314
315     int state;
316
317     if (virDomainGetState(mDom.get(), &state, NULL, 0)) {
318         LOGE(mId << ": Error while getting the container's state:\n"
319              << libvirt::libvirtFormatError());
320         throw ContainerOperationException();
321     }
322
323     return state;
324 }
325
326
327 void ContainerAdmin::setSchedulerLevel(SchedulerLevel sched)
328 {
329     switch (sched) {
330     case SchedulerLevel::FOREGROUND:
331         LOGD(mId << ": Setting SchedulerLevel::FOREGROUND");
332         setSchedulerParams(DEFAULT_CPU_SHARES,
333                            DEFAULT_VCPU_PERIOD_MS,
334                            mConfig.cpuQuotaForeground);
335         break;
336     case SchedulerLevel::BACKGROUND:
337         LOGD(mId << ": Setting SchedulerLevel::BACKGROUND");
338         setSchedulerParams(DEFAULT_CPU_SHARES,
339                            DEFAULT_VCPU_PERIOD_MS,
340                            mConfig.cpuQuotaBackground);
341         break;
342     default:
343         assert(0 && "Unknown sched parameter value");
344     }
345 }
346
347
348 void ContainerAdmin::setSchedulerParams(std::uint64_t cpuShares, std::uint64_t vcpuPeriod, std::int64_t vcpuQuota)
349 {
350     assert(mDom);
351
352     int maxParams = 3;
353     int numParamsBuff = 0;
354
355     std::unique_ptr<virTypedParameter[]> params(new virTypedParameter[maxParams]);
356
357     virTypedParameterPtr paramsTmp = params.get();
358
359     virTypedParamsAddULLong(&paramsTmp, &numParamsBuff, &maxParams, VIR_DOMAIN_SCHEDULER_CPU_SHARES, cpuShares);
360     virTypedParamsAddULLong(&paramsTmp, &numParamsBuff, &maxParams, VIR_DOMAIN_SCHEDULER_VCPU_PERIOD, vcpuPeriod);
361     virTypedParamsAddLLong(&paramsTmp, &numParamsBuff, &maxParams, VIR_DOMAIN_SCHEDULER_VCPU_QUOTA, vcpuQuota);
362
363     if (virDomainSetSchedulerParameters(mDom.get(), params.get(), numParamsBuff) < 0) {
364         LOGE(mId << ": Error while setting the container's scheduler params:\n"
365              << libvirt::libvirtFormatError());
366         throw ContainerOperationException();
367     }
368 }
369
370 void ContainerAdmin::setDetachOnExit()
371 {
372     mDetachOnExit = true;
373 }
374
375 std::int64_t ContainerAdmin::getSchedulerQuota()
376 {
377     assert(mDom);
378
379     int numParamsBuff;
380     std::unique_ptr<char, void(*)(void*)> type(virDomainGetSchedulerType(mDom.get(), &numParamsBuff), free);
381
382     if (type == NULL || numParamsBuff <= 0 || strcmp(type.get(), "posix") != 0) {
383         LOGE(mId << ": Error while getting the container's scheduler type:\n"
384              << libvirt::libvirtFormatError());
385         throw ContainerOperationException();
386     }
387
388     std::unique_ptr<virTypedParameter[]> params(new virTypedParameter[numParamsBuff]);
389
390     if (virDomainGetSchedulerParameters(mDom.get(), params.get(), &numParamsBuff) < 0) {
391         LOGE(mId << ": Error while getting the container's scheduler params:\n"
392              << libvirt::libvirtFormatError());
393         throw ContainerOperationException();
394     }
395
396     long long quota;
397     if (virTypedParamsGetLLong(params.get(),
398                                numParamsBuff,
399                                VIR_DOMAIN_SCHEDULER_VCPU_QUOTA,
400                                &quota) <= 0) {
401         LOGE(mId << ": Error while getting the container's scheduler quota param:\n"
402              << libvirt::libvirtFormatError());
403         throw ContainerOperationException();
404     }
405
406     return quota;
407 }
408
409 ContainerAdmin::ListenerId ContainerAdmin::registerLifecycleListener(const ContainerAdmin::LifecycleListener& listener,
410                                                                      const utils::CallbackGuard::Tracker& tracker)
411 {
412
413     utils::CallbackWrapper<LifecycleListener> wrap(listener, tracker);
414
415     std::unique_lock<std::mutex> lock(mListenerMutex);
416     unsigned int id = mNextIdForListener++;
417     mLifecycleListeners.insert(LifecycleListenerMap::value_type(id, std::move(wrap)));
418
419     return id;
420 }
421
422 ContainerAdmin::ListenerId ContainerAdmin::registerRebootListener(const ContainerAdmin::RebootListener& listener,
423                                                                   const utils::CallbackGuard::Tracker& tracker)
424 {
425
426     utils::CallbackWrapper<RebootListener> wrap(listener, tracker);
427
428     std::unique_lock<std::mutex> lock(mListenerMutex);
429     unsigned int id = mNextIdForListener++;
430     mRebootListeners.insert(RebootListenerMap::value_type(id, std::move(wrap)));
431
432     return id;
433 }
434
435 void ContainerAdmin::removeListener(const ContainerAdmin::ListenerId id)
436 {
437     std::unique_lock<std::mutex> lock(mListenerMutex);
438     mLifecycleListeners.erase(id);
439     mRebootListeners.erase(id);
440 }
441
442 int ContainerAdmin::libvirtLifecycleCallback(virConnectPtr /*con*/,
443                                              virDomainPtr /*dom*/,
444                                              int event,
445                                              int detail,
446                                              void* opaque)
447 {
448     ContainerAdmin* thisPtr = utils::getCallbackFromPointer<ContainerAdmin*>(opaque);
449
450     LOGI(thisPtr->getId()
451          << ": Lifecycle event: "
452          << libvirt::libvirtEventToString(event)
453          << ": "
454          << libvirt::libvirtEventDetailToString(event, detail));
455
456     std::unique_lock<std::mutex> lock(thisPtr->mListenerMutex);
457     for (auto& it : thisPtr->mLifecycleListeners) {
458         LifecycleListener f = it.second.get();
459         f(event, detail);
460     }
461
462     // ignored, libvirt's legacy
463     return 0;
464 }
465
466 void ContainerAdmin::libvirtRebootCallback(virConnectPtr /*con*/,
467                                            virDomainPtr /*dom*/,
468                                            void* opaque)
469 {
470     ContainerAdmin* thisPtr = utils::getCallbackFromPointer<ContainerAdmin*>(opaque);
471
472     LOGI(thisPtr->getId() << ": Reboot event");
473
474     std::unique_lock<std::mutex> lock(thisPtr->mListenerMutex);
475     for (auto& it : thisPtr->mRebootListeners) {
476         RebootListener f = it.second.get();
477         f();
478     }
479 }
480
481
482 } // namespace security_containers