lxcpp: fix cgroup unit tests
[platform/core/security/vasum.git] / server / zone.cpp
1 /*
2  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Contact: Lukasz Pawelczyk <l.pawelczyk@partner.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  Lukasz Pawelczyk (l.pawelczyk@partner.samsung.com)
22  * @brief   Implementation of class for managing one zone
23  */
24
25 #include "config.hpp"
26
27 #include "zone.hpp"
28 #include "dynamic-config-scheme.hpp"
29 #include "exception.hpp"
30
31 #include "logger/logger.hpp"
32 #include "utils/paths.hpp"
33 #include "utils/vt.hpp"
34 #include "utils/c-array.hpp"
35 #include "lxc/cgroup.hpp"
36 #include "config/manager.hpp"
37
38 #include <boost/filesystem.hpp>
39
40 #include <cassert>
41 #include <climits>
42 #include <string>
43 #include <thread>
44 #include <chrono>
45
46 namespace vasum {
47
48 namespace fs = boost::filesystem;
49
50 namespace {
51
52 typedef std::lock_guard<std::recursive_mutex> Lock;
53
54 const std::string STATE_STOPPED = "stopped";
55 const std::string STATE_RUNNING = "running";
56 const std::string STATE_PAUSED = "paused";
57
58 const std::uint64_t DEFAULT_CPU_SHARES = 1024;
59 const std::uint64_t DEFAULT_VCPU_PERIOD_MS = 100000;
60
61 } // namespace
62
63 Zone::Zone(const std::string& zoneId,
64            const std::string& zonesPath,
65            const std::string& zoneTemplatePath,
66            const std::string& dbPath,
67            const std::string& zoneTemplateDir,
68            const std::string& baseRunMountPointPath)
69     : mDbPath(dbPath)
70     , mZone(zonesPath, zoneId)
71     , mId(zoneId)
72     , mDetachOnExit(false)
73     , mDestroyOnExit(false)
74 {
75     LOGD(mId << ": Instantiating Zone object");
76
77     const std::string dbPrefix = getZoneDbPrefix(zoneId);
78     config::loadFromKVStoreWithJsonFile(dbPath, zoneTemplatePath, mConfig, dbPrefix);
79     config::loadFromKVStoreWithJsonFile(dbPath, zoneTemplatePath, mDynamicConfig, dbPrefix);
80
81     if (!mDynamicConfig.runMountPoint.empty()) {
82         mRunMountPoint = fs::absolute(mDynamicConfig.runMountPoint, baseRunMountPointPath).string();
83     }
84
85     if (!mZone.isDefined()) {
86         const std::string zoneTemplate = utils::getAbsolutePath(mConfig.zoneTemplate,
87                                                                 zoneTemplateDir);
88         LOGI(mId << ": Creating zone from template: " << zoneTemplate);
89         utils::CStringArrayBuilder args;
90         if (!mDynamicConfig.ipv4Gateway.empty()) {
91             args.add("--ipv4-gateway");
92             args.add(mDynamicConfig.ipv4Gateway.c_str());
93         }
94         if (!mDynamicConfig.ipv4.empty()) {
95             args.add("--ipv4");
96             args.add(mDynamicConfig.ipv4.c_str());
97         }
98         const std::string vt = std::to_string(mDynamicConfig.vt);
99         if (mDynamicConfig.vt > 0) {
100             args.add("--vt");
101             args.add(vt.c_str());
102         }
103         if (!mZone.create(zoneTemplate, args.c_array())) {
104             throw ZoneOperationException("Could not create zone");
105         }
106     }
107
108     const fs::path zonePath = fs::path(zonesPath) / zoneId;
109     mRootPath = (zonePath / fs::path("rootfs")).string();
110
111     mProvision.reset(new ZoneProvision(mRootPath, zoneTemplatePath, dbPath, dbPrefix, mConfig.validLinkPrefixes));
112 }
113
114 Zone::~Zone()
115 {
116     LOGD(mId << ": Destroying Zone object...");
117
118     if (mDestroyOnExit) {
119         if (!mZone.stop()) {
120             LOGE(mId << ": Failed to stop the zone");
121         }
122         if (!mZone.destroy()) {
123             LOGE(mId << ": Failed to destroy the zone");
124         }
125     }
126
127     if (!mDetachOnExit && !mDestroyOnExit) {
128         // Try to forcefully stop
129         if (!mZone.stop()) {
130             LOGE(mId << ": Failed to stop the zone");
131         }
132     }
133
134     LOGD(mId << ": Zone object destroyed");
135 }
136
137 const std::string& Zone::getId() const
138 {
139     Lock lock(mReconnectMutex);
140     return mId;
141 }
142
143 int Zone::getPrivilege() const
144 {
145     return mConfig.privilege;
146 }
147
148 void Zone::saveDynamicConfig()
149 {
150     config::saveToKVStore(mDbPath, mDynamicConfig, getZoneDbPrefix(mId));
151 }
152
153 void Zone::updateRequestedState(const std::string& state)
154 {
155     // assume mutex is locked
156     if (state != mDynamicConfig.requestedState) {
157         LOGT("Set requested state of " << mId << " to " << state);
158         mDynamicConfig.requestedState = state;
159         saveDynamicConfig();
160     }
161 }
162
163 void Zone::restore()
164 {
165     std::string requestedState;
166     {
167         Lock lock(mReconnectMutex);
168         requestedState = mDynamicConfig.requestedState;
169         LOGT("Requested state of " << mId << ": " << requestedState);
170     }
171
172     if (requestedState == STATE_RUNNING) {
173         start();
174     } else if (requestedState == STATE_STOPPED) {
175     } else if (requestedState == STATE_PAUSED) {
176         start();
177         suspend();
178     } else {
179         LOGE("Invalid requested state: " << requestedState);
180         assert(0 && "invalid requested state");
181     }
182 }
183
184 void Zone::start()
185 {
186     Lock lock(mReconnectMutex);
187
188     LOGD(mId << ": Starting...");
189
190     updateRequestedState(STATE_RUNNING);
191     mProvision->start();
192
193     if (isRunning()) {
194         LOGD(mId << ": Already running - nothing to do...");
195         return;
196     }
197
198     utils::CStringArrayBuilder args;
199     for (const std::string& arg : mConfig.initWithArgs) {
200         args.add(arg.c_str());
201     }
202     if (args.empty()) {
203         args.add("/sbin/init");
204     }
205
206     if (!mZone.start(args.c_array())) {
207         std::string msg = "Could not start zone " + mZone.getName();
208         LOGE(msg);
209         throw ZoneOperationException(msg);
210     }
211
212     // Wait until the full platform launch with graphical stack.
213     // VT should be activated by a graphical stack.
214     // If we do it with 'zoneToFocus.activateVT' before starting the graphical stack,
215     // graphical stack initialization failed and we finally switch to the black screen.
216     // Skip waiting when graphical stack is not running (unit tests).
217     if (mDynamicConfig.vt > 0) {
218         // TODO, timeout is a temporary solution
219         std::this_thread::sleep_for(std::chrono::milliseconds(4000));
220     }
221
222     LOGD(mId << ": Started");
223
224     // Increase cpu quota before connect, otherwise it'd take ages.
225     goForeground();
226     // refocus in ZonesManager will adjust cpu quota after all
227 }
228
229 void Zone::stop(bool saveState)
230 {
231     Lock lock(mReconnectMutex);
232
233     LOGD(mId << ": Stopping procedure started...");
234
235     if (saveState) {
236         updateRequestedState(STATE_STOPPED);
237     }
238     if (isRunning()) {
239         // boost stopping
240         goForeground();
241     }
242
243     if (isStopped()) {
244         LOGD(mId << ": Already crashed/down/off - nothing to do");
245         return;
246     }
247
248     if (!mZone.shutdown(mConfig.shutdownTimeout)) {
249         // force stop
250         if (!mZone.stop()) {
251             throw ZoneOperationException("Could not stop zone");
252         }
253     }
254
255     mProvision->stop();
256
257     LOGD(mId << ": Stopping procedure ended");
258 }
259
260 int Zone::getVT() const
261 {
262     Lock lock(mReconnectMutex);
263     return mDynamicConfig.vt;
264 }
265
266 std::string Zone::getRootPath() const
267 {
268     return mRootPath;
269 }
270
271 bool Zone::activateVT()
272 {
273     Lock lock(mReconnectMutex);
274
275     if (mDynamicConfig.vt >= 0) {
276         return utils::activateVT(mDynamicConfig.vt);
277     }
278
279     return true;
280 }
281
282 void Zone::createNetdevVeth(const std::string& zoneDev,
283                             const std::string& hostDev)
284 {
285     Lock lock(mReconnectMutex);
286     netdev::createVeth(mZone.getInitPid(), zoneDev, hostDev);
287 }
288
289 void Zone::createNetdevMacvlan(const std::string& zoneDev,
290                                const std::string& hostDev,
291                                const uint32_t& mode)
292 {
293     Lock lock(mReconnectMutex);
294     netdev::createMacvlan(mZone.getInitPid(), zoneDev, hostDev, static_cast<macvlan_mode>(mode));
295 }
296
297 void Zone::moveNetdev(const std::string& devId)
298 {
299     Lock lock(mReconnectMutex);
300     netdev::movePhys(mZone.getInitPid(), devId);
301 }
302
303 void Zone::destroyNetdev(const std::string& devId)
304 {
305     Lock lock(mReconnectMutex);
306     netdev::destroyNetdev(devId, mZone.getInitPid());
307 }
308
309 void Zone::goForeground()
310 {
311     Lock lock(mReconnectMutex);
312     setSchedulerLevel(SchedulerLevel::FOREGROUND);
313 }
314
315 void Zone::goBackground()
316 {
317     Lock lock(mReconnectMutex);
318     setSchedulerLevel(SchedulerLevel::BACKGROUND);
319 }
320
321 void Zone::setDetachOnExit()
322 {
323     Lock lock(mReconnectMutex);
324     mDetachOnExit = true;
325 }
326
327 void Zone::setDestroyOnExit()
328 {
329     Lock lock(mReconnectMutex);
330     mDestroyOnExit = true;
331 }
332
333 bool Zone::isRunning()
334 {
335     Lock lock(mReconnectMutex);
336     return mZone.getState() == lxc::LxcZone::State::RUNNING;
337 }
338
339 bool Zone::isStopped()
340 {
341     Lock lock(mReconnectMutex);
342     return mZone.getState() == lxc::LxcZone::State::STOPPED;
343 }
344
345 void Zone::suspend()
346 {
347     Lock lock(mReconnectMutex);
348
349     LOGD(mId << ": Pausing...");
350     if (!mZone.freeze()) {
351         throw ZoneOperationException("Could not pause zone");
352     }
353     LOGD(mId << ": Paused");
354
355     updateRequestedState(STATE_PAUSED);
356 }
357
358 void Zone::resume()
359 {
360     Lock lock(mReconnectMutex);
361
362     LOGD(mId << ": Resuming...");
363     if (!mZone.unfreeze()) {
364         throw ZoneOperationException("Could not resume zone");
365     }
366     LOGD(mId << ": Resumed");
367
368     updateRequestedState(STATE_RUNNING);
369 }
370
371 bool Zone::isPaused()
372 {
373     Lock lock(mReconnectMutex);
374     return mZone.getState() == lxc::LxcZone::State::FROZEN;
375 }
376
377 bool Zone::isSwitchToDefaultAfterTimeoutAllowed() const
378 {
379     return mConfig.switchToDefaultAfterTimeout;
380 }
381
382 int Zone::createFile(const std::string& path, const std::int32_t flags, const std::int32_t mode)
383 {
384     int fd = 0;
385     if (!mZone.createFile(path, flags, mode, &fd)) {
386         throw ZoneOperationException("Failed to create file.");
387     }
388     return fd;
389 }
390
391 std::string Zone::declareFile(const int32_t& type,
392                               const std::string& path,
393                               const int32_t& flags,
394                               const int32_t& mode)
395 {
396     return mProvision->declareFile(type, path, flags, mode);
397 }
398
399 std::string Zone::declareMount(const std::string& source,
400                                const std::string& target,
401                                const std::string& type,
402                                const int64_t& flags,
403                                const std::string& data)
404 {
405     return mProvision->declareMount(source, target, type, flags, data);
406 }
407
408 std::string Zone::declareLink(const std::string& source,
409                               const std::string& target)
410 {
411     return mProvision->declareLink(source, target);
412 }
413
414 std::vector<std::string> Zone::getDeclarations() const
415 {
416     return mProvision->list();
417 }
418
419 void Zone::removeDeclaration(const std::string& declarationId)
420 {
421     mProvision->remove(declarationId);
422 }
423
424 void Zone::setNetdevAttrs(const std::string& netdev, const NetdevAttrs& attrs)
425 {
426     Lock lock(mReconnectMutex);
427     netdev::setAttrs(mZone.getInitPid(), netdev, attrs);
428 }
429
430 Zone::NetdevAttrs Zone::getNetdevAttrs(const std::string& netdev)
431 {
432     Lock lock(mReconnectMutex);
433     return netdev::getAttrs(mZone.getInitPid(), netdev);
434 }
435
436 std::vector<std::string> Zone::getNetdevList()
437 {
438     Lock lock(mReconnectMutex);
439     return netdev::listNetdev(mZone.getInitPid());
440 }
441
442 void Zone::deleteNetdevIpAddress(const std::string& netdev, const std::string& ip)
443 {
444     Lock lock(mReconnectMutex);
445     netdev::deleteIpAddress(mZone.getInitPid(), netdev, ip);
446 }
447
448 std::int64_t Zone::getSchedulerQuota()
449 {
450     std::string ret;
451     if (!lxc::getCgroup(mId, "cpu", "cpu.cfs_quota_us", ret)) {
452         LOGE(mId << ": Error while getting the zone's scheduler quota param");
453         throw ZoneOperationException("Could not get scheduler quota param");
454     }
455     return std::stoll(ret);
456 }
457
458 void Zone::setSchedulerLevel(SchedulerLevel sched)
459 {
460     assert(isRunning());
461
462     switch (sched) {
463     case SchedulerLevel::FOREGROUND:
464         LOGD(mId << ": Setting SchedulerLevel::FOREGROUND");
465         setSchedulerParams(DEFAULT_CPU_SHARES,
466                            DEFAULT_VCPU_PERIOD_MS,
467                            mConfig.cpuQuotaForeground);
468         break;
469     case SchedulerLevel::BACKGROUND:
470         LOGD(mId << ": Setting SchedulerLevel::BACKGROUND");
471         setSchedulerParams(DEFAULT_CPU_SHARES,
472                            DEFAULT_VCPU_PERIOD_MS,
473                            mConfig.cpuQuotaBackground);
474         break;
475     default:
476         assert(0 && "Unknown sched parameter value");
477     }
478 }
479
480 void Zone::setSchedulerParams(std::uint64_t cpuShares,
481                                    std::uint64_t vcpuPeriod,
482                                    std::int64_t vcpuQuota)
483 {
484     assert(vcpuPeriod >= 1000 && vcpuPeriod <= 1000000);
485     assert(vcpuQuota == -1 ||
486            (vcpuQuota >= 1000 && vcpuQuota <= static_cast<std::int64_t>(ULLONG_MAX / 1000)));
487
488     if (!lxc::setCgroup(mId, "cpu", "cpu.shares", std::to_string(cpuShares)) ||
489         !lxc::setCgroup(mId, "cpu", "cpu.cfs_period_us", std::to_string(vcpuPeriod)) ||
490         !lxc::setCgroup(mId, "cpu", "cpu.cfs_quota_us", std::to_string(vcpuQuota))) {
491
492         LOGE(mId << ": Error while setting the zone's scheduler params");
493         throw ZoneOperationException("Could not set scheduler params");
494     }
495 }
496
497 } // namespace vasum