lxcpp: provisioning implementation (part 2)
[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         throw ZoneOperationException("Could not start zone");
208     }
209
210     // Wait until the full platform launch with graphical stack.
211     // VT should be activated by a graphical stack.
212     // If we do it with 'zoneToFocus.activateVT' before starting the graphical stack,
213     // graphical stack initialization failed and we finally switch to the black screen.
214     // Skip waiting when graphical stack is not running (unit tests).
215     if (mDynamicConfig.vt > 0) {
216         // TODO, timeout is a temporary solution
217         std::this_thread::sleep_for(std::chrono::milliseconds(4000));
218     }
219
220     LOGD(mId << ": Started");
221
222     // Increase cpu quota before connect, otherwise it'd take ages.
223     goForeground();
224     // refocus in ZonesManager will adjust cpu quota after all
225 }
226
227 void Zone::stop(bool saveState)
228 {
229     Lock lock(mReconnectMutex);
230
231     LOGD(mId << ": Stopping procedure started...");
232
233     if (saveState) {
234         updateRequestedState(STATE_STOPPED);
235     }
236     if (isRunning()) {
237         // boost stopping
238         goForeground();
239     }
240
241     if (isStopped()) {
242         LOGD(mId << ": Already crashed/down/off - nothing to do");
243         return;
244     }
245
246     if (!mZone.shutdown(mConfig.shutdownTimeout)) {
247         // force stop
248         if (!mZone.stop()) {
249             throw ZoneOperationException("Could not stop zone");
250         }
251     }
252
253     mProvision->stop();
254
255     LOGD(mId << ": Stopping procedure ended");
256 }
257
258 int Zone::getVT() const
259 {
260     Lock lock(mReconnectMutex);
261     return mDynamicConfig.vt;
262 }
263
264 std::string Zone::getRootPath() const
265 {
266     return mRootPath;
267 }
268
269 bool Zone::activateVT()
270 {
271     Lock lock(mReconnectMutex);
272
273     if (mDynamicConfig.vt >= 0) {
274         return utils::activateVT(mDynamicConfig.vt);
275     }
276
277     return true;
278 }
279
280 void Zone::createNetdevVeth(const std::string& zoneDev,
281                             const std::string& hostDev)
282 {
283     Lock lock(mReconnectMutex);
284     netdev::createVeth(mZone.getInitPid(), zoneDev, hostDev);
285 }
286
287 void Zone::createNetdevMacvlan(const std::string& zoneDev,
288                                const std::string& hostDev,
289                                const uint32_t& mode)
290 {
291     Lock lock(mReconnectMutex);
292     netdev::createMacvlan(mZone.getInitPid(), zoneDev, hostDev, static_cast<macvlan_mode>(mode));
293 }
294
295 void Zone::moveNetdev(const std::string& devId)
296 {
297     Lock lock(mReconnectMutex);
298     netdev::movePhys(mZone.getInitPid(), devId);
299 }
300
301 void Zone::destroyNetdev(const std::string& devId)
302 {
303     Lock lock(mReconnectMutex);
304     netdev::destroyNetdev(devId, mZone.getInitPid());
305 }
306
307 void Zone::goForeground()
308 {
309     Lock lock(mReconnectMutex);
310     setSchedulerLevel(SchedulerLevel::FOREGROUND);
311 }
312
313 void Zone::goBackground()
314 {
315     Lock lock(mReconnectMutex);
316     setSchedulerLevel(SchedulerLevel::BACKGROUND);
317 }
318
319 void Zone::setDetachOnExit()
320 {
321     Lock lock(mReconnectMutex);
322     mDetachOnExit = true;
323 }
324
325 void Zone::setDestroyOnExit()
326 {
327     Lock lock(mReconnectMutex);
328     mDestroyOnExit = true;
329 }
330
331 bool Zone::isRunning()
332 {
333     Lock lock(mReconnectMutex);
334     return mZone.getState() == lxc::LxcZone::State::RUNNING;
335 }
336
337 bool Zone::isStopped()
338 {
339     Lock lock(mReconnectMutex);
340     return mZone.getState() == lxc::LxcZone::State::STOPPED;
341 }
342
343 void Zone::suspend()
344 {
345     Lock lock(mReconnectMutex);
346
347     LOGD(mId << ": Pausing...");
348     if (!mZone.freeze()) {
349         throw ZoneOperationException("Could not pause zone");
350     }
351     LOGD(mId << ": Paused");
352
353     updateRequestedState(STATE_PAUSED);
354 }
355
356 void Zone::resume()
357 {
358     Lock lock(mReconnectMutex);
359
360     LOGD(mId << ": Resuming...");
361     if (!mZone.unfreeze()) {
362         throw ZoneOperationException("Could not resume zone");
363     }
364     LOGD(mId << ": Resumed");
365
366     updateRequestedState(STATE_RUNNING);
367 }
368
369 bool Zone::isPaused()
370 {
371     Lock lock(mReconnectMutex);
372     return mZone.getState() == lxc::LxcZone::State::FROZEN;
373 }
374
375 bool Zone::isSwitchToDefaultAfterTimeoutAllowed() const
376 {
377     return mConfig.switchToDefaultAfterTimeout;
378 }
379
380 int Zone::createFile(const std::string& path, const std::int32_t flags, const std::int32_t mode)
381 {
382     int fd = 0;
383     if (!mZone.createFile(path, flags, mode, &fd)) {
384         throw ZoneOperationException("Failed to create file.");
385     }
386     return fd;
387 }
388
389 std::string Zone::declareFile(const int32_t& type,
390                               const std::string& path,
391                               const int32_t& flags,
392                               const int32_t& mode)
393 {
394     return mProvision->declareFile(type, path, flags, mode);
395 }
396
397 std::string Zone::declareMount(const std::string& source,
398                                const std::string& target,
399                                const std::string& type,
400                                const int64_t& flags,
401                                const std::string& data)
402 {
403     return mProvision->declareMount(source, target, type, flags, data);
404 }
405
406 std::string Zone::declareLink(const std::string& source,
407                               const std::string& target)
408 {
409     return mProvision->declareLink(source, target);
410 }
411
412 std::vector<std::string> Zone::getDeclarations() const
413 {
414     return mProvision->list();
415 }
416
417 void Zone::removeDeclaration(const std::string& declarationId)
418 {
419     mProvision->remove(declarationId);
420 }
421
422 void Zone::setNetdevAttrs(const std::string& netdev, const NetdevAttrs& attrs)
423 {
424     Lock lock(mReconnectMutex);
425     netdev::setAttrs(mZone.getInitPid(), netdev, attrs);
426 }
427
428 Zone::NetdevAttrs Zone::getNetdevAttrs(const std::string& netdev)
429 {
430     Lock lock(mReconnectMutex);
431     return netdev::getAttrs(mZone.getInitPid(), netdev);
432 }
433
434 std::vector<std::string> Zone::getNetdevList()
435 {
436     Lock lock(mReconnectMutex);
437     return netdev::listNetdev(mZone.getInitPid());
438 }
439
440 void Zone::deleteNetdevIpAddress(const std::string& netdev, const std::string& ip)
441 {
442     Lock lock(mReconnectMutex);
443     netdev::deleteIpAddress(mZone.getInitPid(), netdev, ip);
444 }
445
446 std::int64_t Zone::getSchedulerQuota()
447 {
448     std::string ret;
449     if (!lxc::getCgroup(mId, "cpu", "cpu.cfs_quota_us", ret)) {
450         LOGE(mId << ": Error while getting the zone's scheduler quota param");
451         throw ZoneOperationException("Could not get scheduler quota param");
452     }
453     return std::stoll(ret);
454 }
455
456 void Zone::setSchedulerLevel(SchedulerLevel sched)
457 {
458     assert(isRunning());
459
460     switch (sched) {
461     case SchedulerLevel::FOREGROUND:
462         LOGD(mId << ": Setting SchedulerLevel::FOREGROUND");
463         setSchedulerParams(DEFAULT_CPU_SHARES,
464                            DEFAULT_VCPU_PERIOD_MS,
465                            mConfig.cpuQuotaForeground);
466         break;
467     case SchedulerLevel::BACKGROUND:
468         LOGD(mId << ": Setting SchedulerLevel::BACKGROUND");
469         setSchedulerParams(DEFAULT_CPU_SHARES,
470                            DEFAULT_VCPU_PERIOD_MS,
471                            mConfig.cpuQuotaBackground);
472         break;
473     default:
474         assert(0 && "Unknown sched parameter value");
475     }
476 }
477
478 void Zone::setSchedulerParams(std::uint64_t cpuShares,
479                                    std::uint64_t vcpuPeriod,
480                                    std::int64_t vcpuQuota)
481 {
482     assert(vcpuPeriod >= 1000 && vcpuPeriod <= 1000000);
483     assert(vcpuQuota == -1 ||
484            (vcpuQuota >= 1000 && vcpuQuota <= static_cast<std::int64_t>(ULLONG_MAX / 1000)));
485
486     if (!lxc::setCgroup(mId, "cpu", "cpu.shares", std::to_string(cpuShares)) ||
487         !lxc::setCgroup(mId, "cpu", "cpu.cfs_period_us", std::to_string(vcpuPeriod)) ||
488         !lxc::setCgroup(mId, "cpu", "cpu.cfs_quota_us", std::to_string(vcpuQuota))) {
489
490         LOGE(mId << ": Error while setting the zone's scheduler params");
491         throw ZoneOperationException("Could not set scheduler params");
492     }
493 }
494
495 } // namespace vasum