Compare commits

...

227 Commits

Author SHA1 Message Date
Tianon Gravi 3c20a8cdc7 Clean out remaining cruft from 'tianon-separate-dockerinit' 2013-10-18 18:19:54 -06:00
Guillaume J. Charmes d5b3299673 Merge branch 'tianon-separate-dockerinit' into dm
Conflicts:
	runtime_test.go
2013-10-18 16:40:15 -07:00
Guillaume J. Charmes 27140018c5 Merge branch 'links' into dm
Conflicts:
	Dockerfile
	docker-init/docker-init.go
	hack/make.sh
	hack/make/binary
	hack/make/test
	runtime.go
	runtime_test.go
	utils.go
2013-10-18 15:40:50 -07:00
Victor Vieux fbf85e2e0a fix merge issue and gofmt 2013-10-18 14:15:24 -07:00
Victor Vieux d596a8a72c no more name encoding 2013-10-18 14:05:53 -07:00
Victor Vieux bf25342e11 replace == by HasPrefix for names and improve error message 2013-10-18 14:05:53 -07:00
Victor Vieux 4f51ad7d0e refactor notrunc in commands.go to add names support 2013-10-18 14:05:53 -07:00
Victor Vieux 2e181c5635 fix typo in docs 2013-10-18 14:05:53 -07:00
Michael Crosby 1db7cb2f01 Updated fixes post rebase from master
Removed test cases that are no longer
applicable with links and the port changes.
Remove test case where a test was hitting an
external ip.
2013-10-18 14:05:53 -07:00
Alexander Larsson e6e6d89cc1 Move SysInit to a submodule
This allows docker-init to not import the main docker
module, which means it won't e.g. pick up any sqlite
dependencies.
2013-10-18 14:05:53 -07:00
Alexander Larsson 1484434196 Move ListOps to utils submodule
This will be needed for later use in docker-init without a docker
dependency
2013-10-18 14:05:53 -07:00
Alexander Larsson 34c7fb0c43 Add a network test
This just tries pinging first 127.0.0.1 and then 8.8.8.8.

We can't ping via a dns name, because libresolv.so is missing
from the test image, so dns resolving doesn't work.
2013-10-18 14:05:53 -07:00
Alexander Larsson 9268e5e42f network: Stop shelling out to /sbin/ip
We have our own netlink version of all the required calls now, so
we can just skip the /sbin/ip dependency.
2013-10-18 14:05:53 -07:00
Alexander Larsson 04f8ffa140 Use netlink directly instead of /bin/ip in Sysinit
The sysinit code only uses /bin/ip to set a default gateway. This
is pretty easy to do via netlink directly, so we can avoid
the ip dependency.
2013-10-18 14:05:53 -07:00
Alexander Larsson 7e3e662ba5 Add netlink submodule
This contains various operations that can replace the use of
shelling out to "ip".
2013-10-18 14:05:52 -07:00
Michael Crosby 2b17d79e5b Add flag for inter-container communication 2013-10-18 14:05:52 -07:00
Solomon Hykes 0fae30e8c3 Typos and FIXMEs 2013-10-18 14:05:52 -07:00
Solomon Hykes 7fff719cf3 gograph: fix a bug which caused the unicity of (parent, name) to not be enforced 2013-10-18 14:05:52 -07:00
Michael Crosby 6f8d479324 Remove docker ls from docs and cli usage 2013-10-18 14:05:52 -07:00
Michael Crosby 4c27690cdd Add links for container relationships and introspection 2013-10-18 14:04:41 -07:00
Guillaume J. Charmes bc92933384 Merge branch 'separate-dockerinit' of https://github.com/tianon/docker into tianon-separate-dockerinit 2013-10-18 12:45:28 -07:00
Guillaume J. Charmes e9ee860c91 Merge branch 'master' into dm
Conflicts:
	Dockerfile
	buildfile.go
	container.go
	hack/make/test
	runtime_test.go
	utils/utils.go
2013-10-18 12:38:29 -07:00
Guillaume J. Charmes c2175ae736 Switch back some Errorf to Debugf. 2013-10-18 12:29:16 -07:00
Tianon Gravi 34365ececa Rename all cases of "docker-init" to "dockerinit" for consistency 2013-10-18 10:32:19 -06:00
Alexander Larsson 7591a69108 Add a separate docker-init binary
This may be used for the .dockerinit case if the main binary is not
statically linked.

Conflicts:
	docker-init/docker-init.go
2013-10-18 10:23:45 -06:00
Alexander Larsson 764ce78c1d Move SysInit to a submodule
This allows docker-init to not import the main docker
module, which means it won't e.g. pick up any sqlite
dependencies.
2013-10-18 10:22:28 -06:00
Alexander Larsson 2812baf395 devmapper: Fix prefix name to work with udev
Udev escapes "," used in device names to 0\x2c which breaks libdevmapper.
Instead use : to escape minor and minor which works.
2013-10-18 11:39:47 +02:00
Alexander Larsson 572b1fd9be devmapper: Fix major/minor extraction
The way devices are mapped is a bit more complex than before.
This implements the method from new_decode_dev in:
https://github.com/mirrors/linux-2.6/blob/master/include/linux/kdev_t.h

Which is what is needed on kernels > 2.6
2013-10-18 11:38:21 +02:00
Solomon Hykes 4756ad248a devmapper: use major,minor of underlying device in devmapper device name, for readability. 2013-10-18 08:30:53 +00:00
Solomon Hykes dfb77274ce devmapper: clearer make the 'unknown base hash' error message more understandable 2013-10-18 08:22:42 +00:00
Solomon Hykes 73545199a8 Merge remote-tracking branch 'alexlarsson/dm-status' into dm
Conflicts:
	devmapper/deviceset.go
2013-10-18 07:17:13 +00:00
Solomon Hykes 0ad35c6746 devmapper: cleanup error reporting 2013-10-18 07:09:13 +00:00
Solomon Hykes 5e363072f5 Merge branch 'dm' of ssh://github.com/dotcloud/docker into dm 2013-10-18 06:51:50 +00:00
Solomon Hykes cad913c57b devmapper: the tests are fast enough that we no longer need a workaround to avoid timeouts 2013-10-18 06:48:20 +00:00
Solomon Hykes 5f58a1fbe4 hack: encode the name of the current test in temporary directories, for easier tracking 2013-10-18 06:47:08 +00:00
Solomon Hykes 07e09d57af Increase readbility of unit tests by using mkRuntime everywhere 2013-10-18 06:44:30 +00:00
Solomon Hykes 9f85a967bb Merge pull request #2269 from alexlarsson/dm-blocksize
devmapper: Use a smaller blocksize for the thin-pool
2013-10-17 22:50:33 -07:00
Solomon Hykes 85a36b3b53 devmapper: rename deviceset_devmapper.go to deviceset.go 2013-10-18 05:19:22 +00:00
Solomon Hykes d3505d836a Merge remote-tracking branch 'alexlarsson/dm-readonly-images' into dm
Conflicts:
	devmapper/deviceset_devmapper.go
2013-10-18 03:40:46 +00:00
Solomon Hykes c47e93fcbe Merge remote-tracking branch 'alexlarsson/fix-tar-leak' into dm-correct 2013-10-18 03:32:47 +00:00
Solomon Hykes 5cd7de5de8 hack: fail tests if there are leftover temp files before or after 2013-10-18 03:31:21 +00:00
Solomon Hykes d034aafac7 devicemapper: Add fixme 2013-10-18 00:58:20 +00:00
Solomon Hykes 4bd6021806 devmapper: Rename DeviceSetDM to DeviceSet 2013-10-18 00:07:56 +00:00
Solomon Hykes ed03dbfe82 Don't add /.docker-id to the container filesystem 2013-10-18 00:07:46 +00:00
Solomon Hykes 330062ef72 Remove race condition caused by double-destroy in 2 tests 2013-10-17 23:59:59 +00:00
Guillaume J. Charmes 31b883b076 Add error checking and error messages 2013-10-17 15:04:14 -07:00
Guillaume J. Charmes bdb3b2a88c Merge pull request #2262 from alexlarsson/dm-dind-use-root-dir
devicemapper: Use device/inode of the root dir, not the image
2013-10-17 11:54:40 -07:00
Alexander Larsson a14496ce89 Devmapper: Mount images readonly when calculating changes
There is no need to have this be writable, and there is a chance
that e.g. atime updates will cause writes to the image which is
bad for disk use wrt sharing between all containers.
2013-10-17 16:14:53 +02:00
Alexander Larsson 8abcc8e713 devmapper: Use a smaller blocksize for the thin-pool
As per the thin provisioning docs for creating the pool:

   $data_block_size gives the smallest unit of disk space that can be
   allocated at a time expressed in units of 512-byte sectors.
   $data_block_size must be between 128 (64KB) and 2097152 (1GB) and a
   multiple of 128 (64KB). $data_block_size cannot be changed after the
   thin-pool is created. People primarily interested in thin provisioning
   may want to use a value such as 1024 (512KB). People doing lots of
   snapshotting may want a smaller value such as 128 (64KB).

The switch from 512 (which we used before) to 128 (recommended above
for lots of snapshoting) means a simple container creation (based on the
mattdm/fedora:f19 image) adds 1 MB of diskspace rather than 3.6.
This seems more in tune with how docker is typically used.
2013-10-17 15:33:00 +02:00
Alexander Larsson d733cdcebb Show devicemapper status in "docker info"
This shows the current global diskspace use in "docker info"
2013-10-17 15:32:07 +02:00
Alexander Larsson 0aee096fd7 TarFilter: Fix leak of tmpfiles
We were leaking the temporary directory that we create in TarFilter,
because the "tmpDir, err := ioutil.TempDir()" call overrides the
tmpDir in the outer scope with a new locally scoped variable.
This means tmpDir is always "" when the cleanup function is called.

Also, we did not call the atExit() function if CmdStream had an
error early on.

On errors in CmdStream(),
2013-10-17 14:46:58 +02:00
Alexander Larsson e6a73e65a2 devicemapper: Use device/inode of the root dir, not the image
This way the devicemapper prefix stays stable even if we're not
using loopback mounted devices.
2013-10-17 09:53:09 +02:00
Solomon Hykes ad2fbd9e87 devmapper: error reporting workaround in waitRemove() 2013-10-17 01:49:51 +00:00
Solomon Hykes e5d7472a0d devmapper: small fixes in error reporting 2013-10-17 01:49:27 +00:00
Solomon Hykes 7d3c7e2b29 devmapper: debug messages 2013-10-17 01:47:03 +00:00
Solomon Hykes f1d07e2dbe devmapper: prefix device names with the dev_id+inode of the data image 2013-10-17 01:46:28 +00:00
Solomon Hykes ad968ef3ef devmapper: add useful comments 2013-10-17 01:42:05 +00:00
Solomon Hykes c688e9b5a6 devmapper: debug messages 2013-10-16 23:27:33 +00:00
Solomon Hykes f3e6d34df2 hack: cleanup devicemapper at the last test 2013-10-16 23:27:00 +00:00
Solomon Hykes ea04f3de72 devmapper: wait for devices to be effectively removed before returning a successful remove 2013-10-16 23:26:37 +00:00
Solomon Hykes 153248b60f devicemapper: remove unused code 2013-10-16 23:23:35 +00:00
Solomon Hykes 1711de4b09 devmapper: wait for devices to be effectively unmounted before removing them 2013-10-16 23:06:07 +00:00
Solomon Hykes 11d695a297 Add debug messages while testing devicemapper 2013-10-16 20:45:59 +00:00
Solomon Hykes acf58362cb Hack: fix tests which didn't cleanup properly 2013-10-16 20:44:15 +00:00
Guillaume J. Charmes 3a246ac3d1 Change default values for devicemapper as variable instead of env 2013-10-16 20:42:50 +00:00
Solomon Hykes 1da335f784 Hack: don't run integration tests in /var/lib/docker/unit-tests; add missing cleanups in a few tests 2013-10-16 20:10:20 +00:00
Solomon Hykes cd61fb2e6f WIP: debugging dm-base-hash + dm-refactor-init 2013-10-15 23:56:04 +00:00
Solomon Hykes fdba1aeed8 Merge branch 'dm-refactor-init' into dm
Conflicts:
	runtime_test.go
2013-10-15 23:27:28 +00:00
Solomon Hykes c9f3e54c31 Merge branch 'dm-base-hash' into dm 2013-10-15 23:09:26 +00:00
Solomon Hykes 3997b8a923 hack: don't set DEBUG when running tests 2013-10-15 23:07:26 +00:00
Solomon Hykes ec885d9180 hack: only run certain tests with TESTFLAGS='-run TestName' make.sh 2013-10-15 23:06:41 +00:00
Solomon Hykes 54b0cd7cd1 Merge branch 'dm-missing-mapper' into dm 2013-10-15 22:51:20 +00:00
Solomon Hykes 66db2ac9d8 Merge branch 'dm-fix-test-2' into dm 2013-10-15 22:42:49 +00:00
Alexander Larsson 5ebaca7e55 devmapper: create device nodes 'on create' instead of 'on resume' 2013-10-15 21:27:47 +00:00
Solomon Hykes 432ff7e3c3 Merge pull request #2227 from dotcloud/dm-rename-loopback
Rename loopback dir to devicemapper
2013-10-15 12:23:43 -07:00
Michael Crosby 80bd64245f Add filesystemtype for containers
If no type is specified then assume aufs.
2013-10-15 11:49:13 -07:00
Michael Crosby 4431e9edb7 Rename loopback dir to devicemapper 2013-10-15 11:30:06 -07:00
Solomon Hykes 7093411a8d Initialize devicemapper in NewRuntimeFromDIrectory 2013-10-15 03:53:48 +00:00
Guillaume J. Charmes 5778ed7db2 Make sure the base device is well created before running the tests 2013-10-14 18:36:18 -07:00
Michael Crosby 3455c1a098 Use incrementing prefix on pool and loopback to allow dind 2013-10-14 17:48:43 -07:00
Michael Crosby 5dd12ba20a Ignore cleanup with /dev/mapper does not exist 2013-10-14 14:23:58 -07:00
Solomon Hykes 5892c8e469 Merge pull request #2199 from alexlarsson/dm-docs
Add some docs for newly exported functions
2013-10-14 13:33:21 -07:00
Solomon Hykes 20e3e8c07d Merge pull request #2200 from alexlarsson/dm-remove-unused
Runtime: Remove unused funtion hasFilesystemSupport()
2013-10-14 13:24:53 -07:00
Solomon Hykes 79c0c4470f Merge pull request #2182 from dotcloud/cleanup-dm-unittests
Clean up better from previous unit-test runs
2013-10-14 12:29:59 -07:00
Solomon Hykes 4dedd9a9aa Merge pull request #2183 from dotcloud/migrate-aufs
Migrate aufs to new device mapper backend
2013-10-14 12:28:28 -07:00
Solomon Hykes 063ebbab68 Merge pull request #2184 from dotcloud/dm-rmi
Do not allow image to be deleted when containers are dependent
2013-10-14 12:27:19 -07:00
Guillaume J. Charmes ea92dc2e8c Add error return to cleanup, use os.OpenFile instead of syscall.Open, Make sure the pools are removed after all other devices 2013-10-14 12:26:46 -07:00
Michael Crosby 7f429e0ceb Do not allow image to be deleted when containers are dependent 2013-10-14 09:47:01 -07:00
Alexander Larsson bb42801cdc Runtime: Remove unused funtion hasFilesystemSupport()
This used to be used to be used to detect AUFS support, but is not
used anymore.
2013-10-14 10:54:50 +02:00
Alexander Larsson 8e4b3a3390 Add some docs for newly exported functions 2013-10-14 10:53:12 +02:00
Solomon Hykes 48070274ee Change the base filesystem size in unit tests 2013-10-12 15:06:53 -07:00
Michael Crosby c3f1bb3287 Allow loopback and base fs sizes set by env var 2013-10-11 20:37:11 -07:00
Michael Crosby 562e4f1e23 Verbose migration add warning for running container
Conflicts:
	hack/make.sh
	runtime.go
	runtime_test.go
2013-10-11 17:11:18 -07:00
Michael Crosby a263e07678 Migrate AUFS containers to devmapper
Conflicts:
	hack/make.sh
	runtime.go
2013-10-11 16:54:08 -07:00
Alexander Larsson e7986da531 Clean up better from previous unit-test runs
This makes sure we unmount existing mounts (as well as removing the
devmapper devices), and it fails with proper logs rather than just
panic()ing.
2013-10-11 16:42:11 -07:00
Alexander Larsson b440ec0136 device-mapper: Move all devicemapper spew to log through utils.Debugf(). 2013-10-11 16:31:06 -07:00
Alexander Larsson c77697a45c devmapper: Move all "raw" libdevmapper wrappers to devmapper.go
This separates out the DeviceSet logic a bit better from the raw
device mapper operations.

devicemapper: Serialize addess to the devicemapper deviceset

This code is not safe to run in multiple threads at the same time,
and neither is libdevmapper.

DeviceMapper: Move deactivate into UnmountDevice

This way the deactivate is atomic wrt othe device mapper operations
and will not fail with EBUSY if someone else starts a devicemapper
operation inbetween unmount and deactivate.

devmapper: Fix loopback mounting regression

Some changes were added to attach_loop_device which added
a perror() in a place that caused it to override errno so that
a later errno != EBUSY failed. This fixes that and cleans up
the error reporting a bit.

devmapper: Build on old kernels without LOOP_CTL_GET_FREE define
2013-10-11 16:30:02 -07:00
Solomon Hykes 1804fcba93 Merged master into device-mapper branch 2013-10-10 12:50:30 -07:00
Victor Vieux f6913592a1 Merge pull request #2087 from alexlarsson/device-mapper-test
Device mapper test branch update
2013-10-04 07:54:43 -07:00
Alexander Larsson aaf1f73bcc Tests: Initialize devicemapper early to avoid it happening in a test
This can take a while and may cause some tests to timeout
2013-10-04 15:47:43 +02:00
Alexander Larsson 9b65c7cf49 hack: Don't just run the "TestRunHostname" test 2013-10-04 15:38:47 +02:00
Alexander Larsson f7e374fb3a Remove overly spewy Debugf 2013-10-04 15:36:30 +02:00
Guillaume J. Charmes f29c500d8d Small fixes 2013-10-03 18:05:07 -07:00
Guillaume J. Charmes b843998718 Small fixes 2013-10-03 18:04:14 -07:00
Alexander Larsson 1a1be5a87c Make sure we mark the libdevmapper /dev/mapper/control fd CLOEXEC
We do a hack to mark it such, because otherwise lxc-start will not
work.
2013-10-03 21:00:16 +02:00
Alexander Larsson 7b58e15b08 Be better at cleaning up leftover from earlier test runs
When running the test inside a docker container we sometimes are left with
leftover device nodes for device mapper devices that no longer exist.
We were panic:ing in this case, but with this change we just remove such
nodes.
2013-10-03 19:54:14 +02:00
Guillaume J. Charmes 8b2f4aab23 Random improvments 2013-10-02 20:18:15 -07:00
Tianon Gravi 06d0843a61 Update Dockerfile and hack to support compiling device-mapper code statically (using go1.2rc1) 2013-10-03 10:32:47 -06:00
Victor Vieux deb05a36e8 rebase 2013-10-03 15:49:28 +00:00
Victor Vieux 55189307d0 disable: don't create device nodes manually if udev is not availabile as we don't have it in dind 2013-10-03 15:47:38 +00:00
Alexander Larsson 55e1782d66 Image: Fix time setting for old kernels
This is a better fix for futimes() on kernels not supporting O_PATH.
The previous fix broke when copying a device, as it tried to open it
and got and error.
2013-09-30 17:35:02 -06:00
Victor Vieux 152302e379 go fmt and aufs support removed 2013-09-30 17:35:02 -06:00
Victor Vieux 72a08a5458 Revert "add a -mount-method flag"
This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7.
2013-09-30 17:35:02 -06:00
Victor Vieux aeb89ffbba add a -mount-method flag 2013-09-30 17:35:02 -06:00
Alexander Larsson 0484b2c325 RootIsShared: Fix root detection
Column 4 is the mount position, column 3 will not always be
"/" for the root. On one of my system its "/root".
2013-09-30 17:35:02 -06:00
Alexander Larsson be6fef0254 Tests: Clean up any old devmapper leftovers before starting tests 2013-09-30 17:35:02 -06:00
Alexander Larsson 071cc18b58 Image.Changes: Deactivate image device after unmounting it
There is no need to keep the image device around if we were the
onces creating the device.
2013-09-30 17:35:02 -06:00
Alexander Larsson 6ec5585501 Add DeviceSet.HasActivatedDevice()
This lets you see if a device has been activated
2013-09-30 17:35:02 -06:00
Alexander Larsson 6f57e8025a image: Unmount before removing device in error paths
The device remove fails unless we unmount first
2013-09-30 17:35:02 -06:00
Alexander Larsson b0a9147fd5 runtime test: Ensure all containers are unmounted at nuke()
Otherwise we may leave around e.g. devmapper mounts
2013-09-30 17:35:02 -06:00
Alexander Larsson a7e876e357 ShellQuoteArguments: Fix quoting
This accidentally used two quotes to start/end each quoted string.
2013-09-30 17:35:01 -06:00
Alexander Larsson ecdbdfdaea Image: unmount device before removing it on failures
If we don't do this the remove will fail due to EBUSY
2013-09-30 17:35:01 -06:00
Alexander Larsson 2c71710b74 image: Handle systems that don't support O_PATH when updating timestamp
Older kernel can't handle O_PATH in open() so this will
fail on dirs and symlinks. For dirs wa can fallback to
the normal Utimes, but for symlinks there is not much to do
but ignore their timestamps.
2013-09-30 17:35:01 -06:00
Alexander Larsson bbc72c85f7 devmapper: Fix loopback mount code
Typo in the loop-control code made it always fall back to the
old method of opening loopback devices.
2013-09-30 17:35:01 -06:00
Alexander Larsson 1a082ed245 applyLayer() use btrfs reflinks if availible
We use the new file copy helper which uses btrfs reflinks if availible.
2013-09-30 17:35:01 -06:00
Alexander Larsson 86421e8b5e Add CopyFile that can use btrfs reflinks if availible 2013-09-30 17:35:01 -06:00
Alexander Larsson 91c69fd353 Remove accidental commit that enabled MountMethodFilesystem 2013-09-30 17:35:01 -06:00
Alexander Larsson 43a7d3d0e9 Add trivial copy-based CoW backend
This creates a container by copying the corresponding files
from the layers into the containers. This is not gonna be very useful
on a developer setup, as there is no copy-on-write or general diskspace
sharing. It also makes container instantiation slower.

However, it may be useful in deployment where we don't always have a lot
of containers running (long-running daemons) and where we don't
do a lot of docker commits.
2013-09-30 17:35:01 -06:00
Alexander Larsson 60f552cac3 Add Changes.ChangesLayers()
This calculates the difference between a set of layers and a
directory tree.
2013-09-30 17:35:01 -06:00
Alexander Larsson ad402763e1 Changes: Better metadata comparison
Change the comparison to better handle files that are copied during
container creation but not actually changed:

* Inode - this will change during a copy
* ctime - this will change during a copy (as we can't set it back)
* blocksize - this will change for sparse files during copy
* size for directories - this can change anytime but doesn't
  necessarily reflect an actual contents change
* Compare mtimes at microsecond precision (as this is what utimes has)
2013-09-30 17:35:01 -06:00
Alexander Larsson 5d2ace3424 Image.applyLayer: Be better at creating identical files
There are some changes here that make the file metadata better match
the layer files:

* Set the mode of the file after the chown, as otherwise the per-group/uid
  specific flags and e.g. sticky bit is lost
* Use lchown instead of chown
* Delay mtime updates to after all other changes so that later file
  creation doesn't change the mtime for the parent directory
* Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks
2013-09-30 17:35:01 -06:00
Alexander Larsson 727e7fccca Change how ChangesDirs() works
Rather than scan the files in the old directory twice to detect the
deletions we now scan both directories twice and then do all the
diffing on the in-memory structure.

This is more efficient, but it also lets us diff more complex things
later that are not exact on-disk trees.
2013-09-30 17:35:01 -06:00
Alexander Larsson 7d566b4f76 RootIsShared() - Fix array out of bounds error
This happened for me on the last (empty) line, but better safe than sorry
so we make the check general.
2013-09-30 17:35:01 -06:00
Alexander Larsson fdbc2695fe devmapper: Move init layer to top rather than bottom
The init layer needs to be topmost to make sure certain files
are always there (for instance, the ubuntu:12.10 image wrongly
has /dev/shm being a symlink to /run/shm, and we need to override
that). However, previously the devmapper code implemented the
init layer by putting it in the base devmapper device, which meant
layers above it could override these files (so that ubuntu:12.10
broke).

So, instead we put the base layer in *each* images devmapper device.
This is "safe" because we still have the pristine layer data
in the layer directory. Also, it means we diff the container
against the image with the init layer applied, so it won't show
up in diffs/commits.
2013-09-30 17:35:01 -06:00
Alexander Larsson 429587779a lxc: Work around lxc-start need for private mounts
lxc-start requires / to be mounted private, otherwise the changes
it does inside the container (both mounts and unmounts) will propagate
out to the host.

We work around this by starting up lxc-start in its own namespace where
we set / to rprivate.

Unfortunately go can't really execute any code between clone and exec,
so we can't do this in a nice way. Instead we have a horrible hack that
use the unshare command, the shell and the mount command...
2013-09-30 17:35:01 -06:00
Alexander Larsson 145024c6cc Utils: Add ShellQuoteArguments 2013-09-30 17:35:01 -06:00
Alexander Larsson c138801073 Container: Inject into the mount, not the rwPath
For device-mapper setups we can't just push the file into the rwPath.
2013-09-30 17:35:01 -06:00
Alexander Larsson 0722786600 api_test: Fix PostContainersCreate
We can't look for the created file in the rwpath, because that
doesn't exist in the device-mapper world, instead look in the
RootfsPath.
2013-09-30 17:35:01 -06:00
Alexander Larsson 5f8e24f842 Runtime: Only remove device on destroy if it exists 2013-09-30 17:35:01 -06:00
Alexander Larsson 0d7ab8db03 graph test: Unmount image via image.Unmount()
This helps us track the unmount
2013-09-30 17:35:01 -06:00
Alexander Larsson ed741f7b27 deviceset: Cleanup device sets on test end
We unmount all mounts and deactivate all device mapper devices to
make sure we're left with no leftovers after the test.
2013-09-30 17:35:01 -06:00
Alexander Larsson 9e64ebb295 DeviceSet: Add UnmountDevice()
Right now this does nothing but add a new layer, but it means
that all DeviceMounts are paired with DeviceUnmounts so that we
can track (and cleanup) active mounts.
2013-09-30 17:35:01 -06:00
Alexander Larsson d951911b23 Always start tests from a clean set of loopback images
This way we don't get any issues with leftovers
2013-09-30 17:35:01 -06:00
Alexander Larsson 7fb60caa5d tests: Store the loopback images for test outside unit-tests
This directory is copied to each test prefix which is really
slow with the large loopback mounts.
2013-09-30 17:35:01 -06:00
Alexander Larsson 76a2ab6e34 Allow specifying the docker client path in _DOCKER_INIT_PATH
I currently need this to get the tests running, otherwise it will
mount the docker.test binary inside the containers, which doesn't
work due to the libdevmapper.so dependency.
2013-09-30 17:35:00 -06:00
Alexander Larsson 6094257b28 Limit the amount of prints during normal runs
This removes some Debugf() calls and chages some direct prints to
Debugf(). This means we don't get a bunch of spew when running the
tests.
2013-09-30 17:35:00 -06:00
Alexander Larsson 52294192b2 Reuse a single DeviceSetDM for all the tests
We wrap the "real" DeviceSet for each test so that we get only
a single device-mapper pool and loopback mounts, but still
separate out the IDs in the tests. This makes the test run
much faster.
2013-09-30 17:35:00 -06:00
Alexander Larsson 381ce94ef4 Add DeviceSetWrapper
This wraps an existing DeviceSet and just adds a prefix to all ids in
it. This will be useful for reusing a single DeviceSet for all the tests
(but with separate ids)
2013-09-30 17:35:00 -06:00
Alexander Larsson 99393cf3cf Delete corresponding Devices when deleting Images
If an image is deleted and there is a corresponding device
for that image we also delete the image.
2013-09-30 17:35:00 -06:00
Alexander Larsson 30890c7763 Runtime: Delete corresponding devices when deleting container 2013-09-30 17:35:00 -06:00
Alexander Larsson b0626f403b Implement container.ExportRW() on device-mapper 2013-09-30 17:34:59 -06:00
Alexander Larsson fda6ff9c27 Make TarFilter more useful
There are a few changes:
* Callers can specify if they want recursive behaviour or not
* All file listings to tar are sent on stdin, to handle long lists better
* We can pass in a list of filenames which will be created as empty
  files in the tarball

This is exactly what we want for the creation of layer tarballs given
a container fs, a set of files to add and a set of whiteout files to create.
2013-09-30 17:34:59 -06:00
Alexander Larsson b86f67126c Archive: Fix up tar commandline arguments in TarFilter()
There is no need to duplicate the compression flags for
every element in the filter.
2013-09-30 17:34:59 -06:00
Alexander Larsson 1c5dc26a7c Implement docker diff for device-mapper
To do diffing we just compare file metadata, so this relies
on things like size and mtime/ctime to catch any changes.
Its *possible* to trick this by updating a file without
changing the size and setting back the mtime/ctime, but
that seems pretty unlikely to happen in reality, and lets
us avoid comparing the actual file data.
2013-09-30 17:34:59 -06:00
Alexander Larsson 8e7cbbff50 devmapper: Base the device-mapper names on the root dir name
This means the default is "docker-*", but for tests we get separate
prefixes for each test.
2013-09-30 17:34:59 -06:00
Alexander Larsson 074f38d493 Image: Always create a .docker-id file in the devices we create
Without this there is really no way to map back from the device-mapper
devices to the actual docker image/container ids in case the json file
somehow got lost
2013-09-30 17:34:59 -06:00
Alexander Larsson a9ec1dbc9b Image: Deactivate image device when unmounting container
There is no need to keep all the device-mapper devices active, we
can just activate them on demand if needed.
2013-09-30 17:34:59 -06:00
Alexander Larsson d2ba3e2005 Image: Initial support for device-mapper mounts
This supports creating images from layers and mounting them
for running a container.

Not supported yet are:
* Creating diffs between images/containers
* Creating layers for new images from a device-mapper container
2013-09-30 17:34:59 -06:00
Alexander Larsson 8f7361279c Runtime: Add MountMethod to allow AUFS and device-mapper to coexist 2013-09-30 17:34:59 -06:00
Alexander Larsson ca2f7f955e Runtime: Add DeviceSet singleton
This adds a DeviceSet singleton to the Runtime object which will be used for
any DeviceMapper dependent code.
2013-09-30 17:34:59 -06:00
Alexander Larsson 1d36b8c7b7 Server: Pass in device-mapper DeviceSet to server
This makes docker (but not docker-init) link to libdevmapper and will
allow it to use the DeviceSet
2013-09-30 17:34:59 -06:00
Alexander Larsson e6216793d9 Add DeviceSet interface
This interface matches the device-mapper implementation (DeviceSetDM)
but is free from any dependencies. This allows core docker code
to refer to a DeviceSet without having an explicit dependency on
the devmapper package.

This is important, because the devmapper package has external
dependencies which are not wanted in the docker client app, as it
needs to run with minimal dependencies in the docker image.
2013-09-30 17:34:58 -06:00
Alexander Larsson e368c8bb01 Image: Add runtime and container id args to Mount()
We will later need the runtime to get access to the VolumeSet
singleton, and the container id to have a name for the volume
for the container
2013-09-30 17:34:58 -06:00
Alexander Larsson 167601e858 Runtime: Automatically use docker-init if it exists
In some builds the main docker binary is not statically linked,
and as such not usable in as the .dockerinit binary, for those
cases we look for a separately shipped docker-init binary and
use that instead.
2013-09-30 17:34:58 -06:00
Alexander Larsson b8dc7b5f1a Add a separate docker-init binary
This may be used for the .dockerinit case if the main binary is not
statically linked.
2013-09-30 17:34:58 -06:00
Alexander Larsson 7fb3bfed03 devmapper: Add simple tool to test the DeviceSet commands 2013-09-30 17:34:58 -06:00
Alexander Larsson 374a5e9913 devmapper: Add DeviceSet device-mapper helper
This is a module that uses the device-mapper create CoW snapshots
You instantiate a DeviceSetDM object on a specified root (/var/lib/docker),
and it will create a subdirectory there called "loopback". It will
contain two sparse files which are loopback mounted into
a thin-pool device-mapper device called "docker-pool".

We then create a base snapshot in the pool with an empty filesystem
which can be used as a base for docker snapshots. It also keeps track
of the mapping between docker image ids and the snapshots in the pool.

Typical use of is something like (without error checking):

devices = NewDeviceSetDM("/var/lib/docker")
devices.AddDevice(imageId, "") // "" is the base image id
devices.MountDevice(imageId, "/mnt/image")
 ... extract base image to /mnt/image
devices.AddDevice(containerId, imageId)
devices.MountDevice(containerId, "/mnt/container")
... start container at /mnt/container
2013-09-30 17:34:58 -06:00
Alexander Larsson 459bac7127 Add libdevmapper wrapper 2013-09-30 17:34:58 -06:00
Victor Vieux 514886c73d Merge pull request #2023 from alexlarsson/old_kernel
Image: Fix time setting for old kernels
2013-09-27 05:25:13 -07:00
Alexander Larsson 75e958bf48 Image: Fix time setting for old kernels
This is a better fix for futimes() on kernels not supporting O_PATH.
The previous fix broke when copying a device, as it tried to open it
and got and error.
2013-09-26 23:59:37 +02:00
Victor Vieux ebfa24acb0 go fmt and aufs support removed 2013-09-26 15:40:13 +00:00
Victor Vieux 5e1d540209 Revert "add a -mount-method flag"
This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7.
2013-09-26 15:14:03 +00:00
Victor Vieux c1e25d7273 add a -mount-method flag 2013-09-26 15:10:01 +00:00
Alexander Larsson d263aa6ca9 RootIsShared: Fix root detection
Column 4 is the mount position, column 3 will not always be
"/" for the root. On one of my system its "/root".
2013-09-26 15:09:33 +00:00
Alexander Larsson 03320f0d1c Tests: Clean up any old devmapper leftovers before starting tests 2013-09-26 15:09:33 +00:00
Alexander Larsson 6c7ae06435 Image.Changes: Deactivate image device after unmounting it
There is no need to keep the image device around if we were the
onces creating the device.
2013-09-26 15:09:33 +00:00
Alexander Larsson 395bce4c41 Add DeviceSet.HasActivatedDevice()
This lets you see if a device has been activated
2013-09-26 15:09:33 +00:00
Alexander Larsson 41399ac005 image: Unmount before removing device in error paths
The device remove fails unless we unmount first
2013-09-26 15:09:33 +00:00
Alexander Larsson 67788723c9 runtime test: Ensure all containers are unmounted at nuke()
Otherwise we may leave around e.g. devmapper mounts
2013-09-26 15:09:33 +00:00
Alexander Larsson f99f39abaa ShellQuoteArguments: Fix quoting
This accidentally used two quotes to start/end each quoted string.
2013-09-26 15:09:33 +00:00
Alexander Larsson 009d0f9d81 Image: unmount device before removing it on failures
If we don't do this the remove will fail due to EBUSY
2013-09-26 15:09:32 +00:00
Alexander Larsson ed65815613 image: Handle systems that don't support O_PATH when updating timestamp
Older kernel can't handle O_PATH in open() so this will
fail on dirs and symlinks. For dirs wa can fallback to
the normal Utimes, but for symlinks there is not much to do
but ignore their timestamps.
2013-09-26 15:09:32 +00:00
Alexander Larsson cc28829429 devmapper: Fix loopback mount code
Typo in the loop-control code made it always fall back to the
old method of opening loopback devices.
2013-09-26 15:09:32 +00:00
Alexander Larsson 062a2b32e9 applyLayer() use btrfs reflinks if availible
We use the new file copy helper which uses btrfs reflinks if availible.
2013-09-26 15:09:32 +00:00
Alexander Larsson cda8754013 Add CopyFile that can use btrfs reflinks if availible 2013-09-26 15:09:32 +00:00
Alexander Larsson 5415804c9d Remove accidental commit that enabled MountMethodFilesystem 2013-09-26 15:09:32 +00:00
Alexander Larsson adae684987 Add trivial copy-based CoW backend
This creates a container by copying the corresponding files
from the layers into the containers. This is not gonna be very useful
on a developer setup, as there is no copy-on-write or general diskspace
sharing. It also makes container instantiation slower.

However, it may be useful in deployment where we don't always have a lot
of containers running (long-running daemons) and where we don't
do a lot of docker commits.
2013-09-26 15:09:32 +00:00
Alexander Larsson ad0a6a03e3 Add Changes.ChangesLayers()
This calculates the difference between a set of layers and a
directory tree.
2013-09-26 15:09:32 +00:00
Alexander Larsson 36603e68e3 Changes: Better metadata comparison
Change the comparison to better handle files that are copied during
container creation but not actually changed:

* Inode - this will change during a copy
* ctime - this will change during a copy (as we can't set it back)
* blocksize - this will change for sparse files during copy
* size for directories - this can change anytime but doesn't
  necessarily reflect an actual contents change
* Compare mtimes at microsecond precision (as this is what utimes has)
2013-09-26 15:09:32 +00:00
Alexander Larsson 99c7d129f4 Image.applyLayer: Be better at creating identical files
There are some changes here that make the file metadata better match
the layer files:

* Set the mode of the file after the chown, as otherwise the per-group/uid
  specific flags and e.g. sticky bit is lost
* Use lchown instead of chown
* Delay mtime updates to after all other changes so that later file
  creation doesn't change the mtime for the parent directory
* Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks
2013-09-26 15:09:32 +00:00
Alexander Larsson 02b5f1369c Change how ChangesDirs() works
Rather than scan the files in the old directory twice to detect the
deletions we now scan both directories twice and then do all the
diffing on the in-memory structure.

This is more efficient, but it also lets us diff more complex things
later that are not exact on-disk trees.
2013-09-26 15:09:32 +00:00
Alexander Larsson d478a4bb54 RootIsShared() - Fix array out of bounds error
This happened for me on the last (empty) line, but better safe than sorry
so we make the check general.
2013-09-26 15:09:32 +00:00
Alexander Larsson c199ed228b devmapper: Move init layer to top rather than bottom
The init layer needs to be topmost to make sure certain files
are always there (for instance, the ubuntu:12.10 image wrongly
has /dev/shm being a symlink to /run/shm, and we need to override
that). However, previously the devmapper code implemented the
init layer by putting it in the base devmapper device, which meant
layers above it could override these files (so that ubuntu:12.10
broke).

So, instead we put the base layer in *each* images devmapper device.
This is "safe" because we still have the pristine layer data
in the layer directory. Also, it means we diff the container
against the image with the init layer applied, so it won't show
up in diffs/commits.
2013-09-26 15:09:32 +00:00
Alexander Larsson e40f5c7cb9 lxc: Work around lxc-start need for private mounts
lxc-start requires / to be mounted private, otherwise the changes
it does inside the container (both mounts and unmounts) will propagate
out to the host.

We work around this by starting up lxc-start in its own namespace where
we set / to rprivate.

Unfortunately go can't really execute any code between clone and exec,
so we can't do this in a nice way. Instead we have a horrible hack that
use the unshare command, the shell and the mount command...
2013-09-26 15:09:32 +00:00
Alexander Larsson d80be57c15 Utils: Add ShellQuoteArguments 2013-09-26 15:09:32 +00:00
Alexander Larsson 20bac716b5 Container: Inject into the mount, not the rwPath
For device-mapper setups we can't just push the file into the rwPath.
2013-09-26 15:09:32 +00:00
Alexander Larsson 2566e2604c api_test: Fix PostContainersCreate
We can't look for the created file in the rwpath, because that
doesn't exist in the device-mapper world, instead look in the
RootfsPath.
2013-09-26 15:09:32 +00:00
Alexander Larsson e1c418cac3 Runtime: Only remove device on destroy if it exists 2013-09-26 15:09:32 +00:00
Alexander Larsson 3343b3f8f8 graph test: Unmount image via image.Unmount()
This helps us track the unmount
2013-09-26 15:09:32 +00:00
Alexander Larsson c6e8813c97 deviceset: Cleanup device sets on test end
We unmount all mounts and deactivate all device mapper devices to
make sure we're left with no leftovers after the test.
2013-09-26 15:09:32 +00:00
Alexander Larsson 251a7ed437 DeviceSet: Add UnmountDevice()
Right now this does nothing but add a new layer, but it means
that all DeviceMounts are paired with DeviceUnmounts so that we
can track (and cleanup) active mounts.
2013-09-26 15:09:31 +00:00
Alexander Larsson 261b0b01df Always start tests from a clean set of loopback images
This way we don't get any issues with leftovers
2013-09-26 15:09:31 +00:00
Alexander Larsson a7fd1fce5d tests: Store the loopback images for test outside unit-tests
This directory is copied to each test prefix which is really
slow with the large loopback mounts.
2013-09-26 15:09:31 +00:00
Alexander Larsson 6938a36c69 Allow specifying the docker client path in _DOCKER_INIT_PATH
I currently need this to get the tests running, otherwise it will
mount the docker.test binary inside the containers, which doesn't
work due to the libdevmapper.so dependency.
2013-09-26 15:09:31 +00:00
Alexander Larsson bc7fa7b957 Limit the amount of prints during normal runs
This removes some Debugf() calls and chages some direct prints to
Debugf(). This means we don't get a bunch of spew when running the
tests.
2013-09-26 15:09:31 +00:00
Alexander Larsson d47c18c5fb Reuse a single DeviceSetDM for all the tests
We wrap the "real" DeviceSet for each test so that we get only
a single device-mapper pool and loopback mounts, but still
separate out the IDs in the tests. This makes the test run
much faster.
2013-09-26 15:09:31 +00:00
Alexander Larsson 0e686fa2f4 Add DeviceSetWrapper
This wraps an existing DeviceSet and just adds a prefix to all ids in
it. This will be useful for reusing a single DeviceSet for all the tests
(but with separate ids)
2013-09-26 15:09:31 +00:00
Alexander Larsson 3f3f5f0bba Delete corresponding Devices when deleting Images
If an image is deleted and there is a corresponding device
for that image we also delete the image.
2013-09-26 15:09:31 +00:00
Alexander Larsson 19ba0b851b Runtime: Delete corresponding devices when deleting container 2013-09-26 15:08:55 +00:00
Alexander Larsson 94fa3c7bb5 Implement container.ExportRW() on device-mapper 2013-09-26 15:08:55 +00:00
Alexander Larsson 223280f319 Make TarFilter more useful
There are a few changes:
* Callers can specify if they want recursive behaviour or not
* All file listings to tar are sent on stdin, to handle long lists better
* We can pass in a list of filenames which will be created as empty
  files in the tarball

This is exactly what we want for the creation of layer tarballs given
a container fs, a set of files to add and a set of whiteout files to create.
2013-09-26 15:08:55 +00:00
Alexander Larsson 8f23945f7f Archive: Fix up tar commandline arguments in TarFilter()
There is no need to duplicate the compression flags for
every element in the filter.
2013-09-26 15:08:55 +00:00
Alexander Larsson 8e8ef7cb5b Implement docker diff for device-mapper
To do diffing we just compare file metadata, so this relies
on things like size and mtime/ctime to catch any changes.
Its *possible* to trick this by updating a file without
changing the size and setting back the mtime/ctime, but
that seems pretty unlikely to happen in reality, and lets
us avoid comparing the actual file data.
2013-09-26 15:08:55 +00:00
Alexander Larsson 8f343ea65a devmapper: Base the device-mapper names on the root dir name
This means the default is "docker-*", but for tests we get separate
prefixes for each test.
2013-09-26 15:08:55 +00:00
Alexander Larsson b125f2334c Image: Always create a .docker-id file in the devices we create
Without this there is really no way to map back from the device-mapper
devices to the actual docker image/container ids in case the json file
somehow got lost
2013-09-26 15:08:55 +00:00
Alexander Larsson a89a51128e Image: Deactivate image device when unmounting container
There is no need to keep all the device-mapper devices active, we
can just activate them on demand if needed.
2013-09-26 15:08:54 +00:00
Alexander Larsson fcd41fe51a Image: Initial support for device-mapper mounts
This supports creating images from layers and mounting them
for running a container.

Not supported yet are:
* Creating diffs between images/containers
* Creating layers for new images from a device-mapper container
2013-09-26 15:08:54 +00:00
Alexander Larsson 53851474c0 Runtime: Add MountMethod to allow AUFS and device-mapper to coexist 2013-09-26 15:08:54 +00:00
Alexander Larsson f317a6b6fe Runtime: Add DeviceSet singleton
This adds a DeviceSet singleton to the Runtime object which will be used for
any DeviceMapper dependent code.
2013-09-26 15:08:54 +00:00
Alexander Larsson 87e248f524 Server: Pass in device-mapper DeviceSet to server
This makes docker (but not docker-init) link to libdevmapper and will
allow it to use the DeviceSet
2013-09-26 15:08:54 +00:00
Alexander Larsson ac194fc696 Add DeviceSet interface
This interface matches the device-mapper implementation (DeviceSetDM)
but is free from any dependencies. This allows core docker code
to refer to a DeviceSet without having an explicit dependency on
the devmapper package.

This is important, because the devmapper package has external
dependencies which are not wanted in the docker client app, as it
needs to run with minimal dependencies in the docker image.
2013-09-26 15:08:54 +00:00
Alexander Larsson 8637ba710e Image: Add runtime and container id args to Mount()
We will later need the runtime to get access to the VolumeSet
singleton, and the container id to have a name for the volume
for the container
2013-09-26 15:08:54 +00:00
Alexander Larsson 0f5ccf934e Runtime: Automatically use docker-init if it exists
In some builds the main docker binary is not statically linked,
and as such not usable in as the .dockerinit binary, for those
cases we look for a separately shipped docker-init binary and
use that instead.
2013-09-26 15:08:54 +00:00
Alexander Larsson 250bc3f615 Add a separate docker-init binary
This may be used for the .dockerinit case if the main binary is not
statically linked.
2013-09-26 15:08:54 +00:00
Alexander Larsson 2b1dc8a8a3 devmapper: Add simple tool to test the DeviceSet commands 2013-09-26 15:08:54 +00:00
Alexander Larsson 0b12702c0c devmapper: Add DeviceSet device-mapper helper
This is a module that uses the device-mapper create CoW snapshots
You instantiate a DeviceSetDM object on a specified root (/var/lib/docker),
and it will create a subdirectory there called "loopback". It will
contain two sparse files which are loopback mounted into
a thin-pool device-mapper device called "docker-pool".

We then create a base snapshot in the pool with an empty filesystem
which can be used as a base for docker snapshots. It also keeps track
of the mapping between docker image ids and the snapshots in the pool.

Typical use of is something like (without error checking):

devices = NewDeviceSetDM("/var/lib/docker")
devices.AddDevice(imageId, "") // "" is the base image id
devices.MountDevice(imageId, "/mnt/image")
 ... extract base image to /mnt/image
devices.AddDevice(containerId, imageId)
devices.MountDevice(containerId, "/mnt/container")
... start container at /mnt/container
2013-09-26 15:08:54 +00:00
Alexander Larsson 739af0a17f Add libdevmapper wrapper 2013-09-26 15:08:54 +00:00
63 changed files with 7847 additions and 1080 deletions
+39 -27
View File
@@ -24,46 +24,58 @@
#
docker-version 0.6.1
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
from ubuntu:12.10
maintainer Solomon Hykes <solomon@dotcloud.com>
# Build dependencies
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
run apt-get install -y -q build-essential
run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
run apt-get install -y -q build-essential
run apt-get install -y -q libsqlite3-dev
# Install Go from source (for eventual cross-compiling)
env CGO_ENABLED 0
run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot
run cd /goroot/src && ./make.bash
env GOROOT /goroot
env PATH $PATH:/goroot/bin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot
run cd /goroot/src && ./make.bash
env GOROOT /goroot
env PATH $PATH:/goroot/bin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
# Create Go cache with tag netgo (for static compilation of Go while preserving CGO support)
run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
# Get lvm2 source for compiling statically
run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2
run cd /lvm2 && git checkout v2_02_102
# can't use git clone -b because it's not supported by git versions before 1.7.10
run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# Ubuntu stuff
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
run gem install --no-rdoc --no-ri fpm
run apt-get install -y -q reprepro dpkg-sig
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
run gem install --no-rdoc --no-ri fpm
run apt-get install -y -q reprepro dpkg-sig
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
# Runtime dependencies
run apt-get install -y -q iptables
run apt-get install -y -q lxc
run apt-get install -y -q aufs-tools
run apt-get install -y -q iptables
run dpkg-divert --local --rename --add /sbin/initctl && \
ln -s /bin/true /sbin/initctl && \
apt-get install -y -q lxc
volume /var/lib/docker
workdir /go/src/github.com/dotcloud/docker
volume /var/lib/docker
workdir /go/src/github.com/dotcloud/docker
# Wrap all commands in the "docker-in-docker" script to allow nested containers
entrypoint ["hack/dind"]
# Upload docker source
add . /go/src/github.com/dotcloud/docker
add . /go/src/github.com/dotcloud/docker
+88 -8
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"github.com/gorilla/mux"
"io"
@@ -69,12 +70,12 @@ func httpError(w http.ResponseWriter, err error) {
statusCode = http.StatusUnauthorized
} else if strings.Contains(err.Error(), "hasn't been activated") {
statusCode = http.StatusForbidden
}
}
if err != nil {
utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error())
http.Error(w, err.Error(), statusCode)
}
http.Error(w, err.Error(), statusCode)
}
}
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
@@ -515,16 +516,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
return err
}
if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
config.Dns = defaultDns
}
id, err := srv.ContainerCreate(config)
id, warnings, err := srv.ContainerCreate(config)
if err != nil {
return err
}
out.ID = id
for _, warning := range warnings {
out.Warnings = append(out.Warnings, warning)
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
@@ -570,12 +574,17 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
removeVolume, err := getBoolParam(r.Form.Get("v"))
if err != nil {
return err
}
removeLink, err := getBoolParam(r.Form.Get("link"))
if err != nil {
return err
}
if err := srv.ContainerDestroy(name, removeVolume); err != nil {
if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -623,6 +632,7 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
}
name := vars["name"]
if err := srv.ContainerStart(name, hostConfig); err != nil {
utils.Debugf("error ContainerStart: %s", err)
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -655,6 +665,7 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
status, err := srv.ContainerWait(name)
if err != nil {
return err
@@ -975,7 +986,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
if err != nil {
version = APIVERSION
}
if srv.enableCors {
if srv.runtime.config.EnableCors {
writeCorsHeaders(w, r)
}
@@ -991,6 +1002,73 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
}
}
func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
runtime := srv.runtime
all, err := getBoolParam(r.Form.Get("all"))
if err != nil {
return err
}
out := []APILink{}
err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
if container := runtime.Get(e.ID()); container != nil {
if !all && strings.Contains(p, container.ID) {
return nil
}
out = append(out, APILink{
Path: p,
ContainerID: container.ID,
Image: runtime.repositories.ImageName(container.Image),
})
}
return nil
}, -1)
if err != nil {
return err
}
return writeJSON(w, http.StatusOK, out)
}
func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
values := make(map[string]string)
if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil {
defer r.Body.Close()
dec := json.NewDecoder(r.Body)
if err := dec.Decode(&values); err != nil {
return err
}
} else {
return fmt.Errorf("Invalid json body")
}
currentName := values["currentName"]
newName := values["newName"]
if currentName == "" {
return fmt.Errorf("currentName cannot be empty")
}
if newName == "" {
return fmt.Errorf("newName cannot be empty")
}
if err := srv.runtime.RenameLink(currentName, newName); err != nil {
if strings.HasSuffix(err.Error(), "name are not unique") {
return fmt.Errorf("Conflict, %s already exists", newName)
}
return err
}
return nil
}
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
r := mux.NewRouter()
@@ -1011,6 +1089,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/attach/ws": wsContainersAttach,
"/containers/links": getContainersLinks,
},
"POST": {
"/auth": postAuth,
@@ -1029,6 +1108,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy,
"/containers/link": postContainerLink,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
+32 -29
View File
@@ -1,7 +1,5 @@
package docker
import "encoding/json"
type APIHistory struct {
ID string `json:"Id"`
Tags []string `json:",omitempty"`
@@ -19,18 +17,23 @@ type APIImages struct {
}
type APIInfo struct {
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
DevmapperPool string `json:",omitempty"`
DevmapperDataUsed uint64 `json:",omitempty"`
DevmapperDataTotal uint64 `json:",omitempty"`
DevmapperMetadataUsed uint64 `json:",omitempty"`
DevmapperMetadataTotal uint64 `json:",omitempty"`
}
type APITop struct {
@@ -52,17 +55,18 @@ type APIContainers struct {
Ports []APIPort
SizeRw int64
SizeRootFs int64
Names []string
}
func (self *APIContainers) ToLegacy() APIContainersOld {
return APIContainersOld{
ID: self.ID,
Image: self.Image,
Command: self.Command,
Created: self.Created,
Status: self.Status,
Ports: displayablePorts(self.Ports),
SizeRw: self.SizeRw,
ID: self.ID,
Image: self.Image,
Command: self.Command,
Created: self.Created,
Status: self.Status,
Ports: displayablePorts(self.Ports),
SizeRw: self.SizeRw,
SizeRootFs: self.SizeRootFs,
}
}
@@ -96,14 +100,7 @@ type APIPort struct {
PrivatePort int64
PublicPort int64
Type string
}
func (port *APIPort) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"PrivatePort": port.PrivatePort,
"PublicPort": port.PublicPort,
"Type": port.Type,
})
IP string
}
type APIVersion struct {
@@ -129,3 +126,9 @@ type APICopy struct {
Resource string
HostPath string
}
type APILink struct {
Path string
ContainerID string
Image string
}
+34 -22
View File
@@ -347,7 +347,7 @@ func TestGetContainersJSON(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
})
@@ -362,9 +362,11 @@ func TestGetContainersJSON(t *testing.T) {
}
r := httptest.NewRecorder()
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
setTimeout(t, "getContainerJSON timed out", 5*time.Second, func() {
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
})
containers := []APIContainers{}
if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
t.Fatal(err)
@@ -384,7 +386,7 @@ func TestGetContainersExport(t *testing.T) {
srv := &Server{runtime: runtime}
// Create a container and remove a file
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
@@ -400,7 +402,7 @@ func TestGetContainersExport(t *testing.T) {
}
r := httptest.NewRecorder()
if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
if err := getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
@@ -434,7 +436,7 @@ func TestGetContainersChanges(t *testing.T) {
srv := &Server{runtime: runtime}
// Create a container and remove a file
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
@@ -477,7 +479,7 @@ func TestGetContainersTop(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "cat"},
@@ -559,7 +561,7 @@ func TestGetContainersByName(t *testing.T) {
srv := &Server{runtime: runtime}
// Create a container and remove a file
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
@@ -590,7 +592,7 @@ func TestPostCommit(t *testing.T) {
srv := &Server{runtime: runtime}
// Create a container and remove a file
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
@@ -669,13 +671,21 @@ func TestPostContainersCreate(t *testing.T) {
t.Fatal(err)
}
if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil {
if err := container.EnsureMounted(); err != nil {
t.Fatalf("Unable to mount container: %s", err)
}
if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil {
if os.IsNotExist(err) {
utils.Debugf("Err: %s", err)
t.Fatalf("The test file has not been created")
}
t.Fatal(err)
}
if err := container.Unmount(); err != nil {
t.Fatalf("Unable to unmount container: %s", err)
}
}
func TestPostContainersKill(t *testing.T) {
@@ -684,7 +694,7 @@ func TestPostContainersKill(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
@@ -726,7 +736,7 @@ func TestPostContainersRestart(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
@@ -780,7 +790,7 @@ func TestPostContainersStart(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
@@ -832,7 +842,7 @@ func TestPostContainersStop(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
@@ -879,7 +889,7 @@ func TestPostContainersWait(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sleep", "1"},
@@ -921,7 +931,7 @@ func TestPostContainersAttach(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
@@ -1010,7 +1020,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"},
@@ -1102,7 +1112,7 @@ func TestDeleteContainers(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
})
@@ -1140,7 +1150,8 @@ func TestOptionsRoute(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true}
runtime.config.EnableCors = true
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
router, err := createRouter(srv, false)
@@ -1163,7 +1174,8 @@ func TestGetEnabledCors(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true}
runtime.config.EnableCors = true
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
@@ -1290,7 +1302,7 @@ func TestPostContainersCopy(t *testing.T) {
srv := &Server{runtime: runtime}
// Create a container and remove a file
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test.txt"},
+85 -8
View File
@@ -80,20 +80,74 @@ func (compression *Compression) Extension() string {
// Tar creates an archive from the directory at `path`, and returns it as a
// stream of bytes.
func Tar(path string, compression Compression) (io.Reader, error) {
return TarFilter(path, compression, nil)
return TarFilter(path, compression, nil, true, nil)
}
func escapeName(name string) string {
escaped := make([]byte, 0)
for i, c := range []byte(name) {
if i == 0 && c == '/' {
continue
}
// all printable chars except "-" which is 0x2d
if (0x20 <= c && c <= 0x7E) && c != 0x2d {
escaped = append(escaped, c)
} else {
escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
}
}
return string(escaped)
}
// Tar creates an archive from the directory at `path`, only including files whose relative
// paths are included in `filter`. If `filter` is nil, then all files are included.
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) {
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"}
if filter == nil {
filter = []string{"."}
}
for _, f := range filter {
args = append(args, "-c"+compression.Flag(), f)
args = append(args, "-c"+compression.Flag())
if !recursive {
args = append(args, "--no-recursion")
}
return CmdStream(exec.Command(args[0], args[1:]...))
files := ""
for _, f := range filter {
files = files + escapeName(f) + "\n"
}
tmpDir := ""
if createFiles != nil {
var err error // Can't use := here or we override the outer tmpDir
tmpDir, err = ioutil.TempDir("", "docker-tar")
if err != nil {
return nil, err
}
files = files + "-C" + tmpDir + "\n"
for _, f := range createFiles {
path := filepath.Join(tmpDir, f)
err := os.MkdirAll(filepath.Dir(path), 0600)
if err != nil {
return nil, err
}
if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil {
return nil, err
} else {
file.Close()
}
files = files + escapeName(f) + "\n"
}
}
return CmdStream(exec.Command(args[0], args[1:]...), &files, func() {
if tmpDir != "" {
_ = os.RemoveAll(tmpDir)
}
})
}
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
@@ -140,7 +194,7 @@ func Untar(archive io.Reader, path string) error {
// TarUntar aborts and returns the error.
func TarUntar(src string, filter []string, dst string) error {
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
archive, err := TarFilter(src, Uncompressed, filter)
archive, err := TarFilter(src, Uncompressed, filter, true, nil)
if err != nil {
return err
}
@@ -227,13 +281,33 @@ func CopyFileWithTar(src, dst string) error {
// CmdStream executes a command, and returns its stdout as a stream.
// If the command fails to run or doesn't complete successfully, an error
// will be returned, including anything written on stderr.
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
if input != nil {
stdin, err := cmd.StdinPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
// Write stdin if any
go func() {
_, _ = stdin.Write([]byte(*input))
stdin.Close()
}()
}
stdout, err := cmd.StdoutPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
pipeR, pipeW := io.Pipe()
@@ -258,6 +332,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
} else {
pipeW.Close()
}
if atEnd != nil {
atEnd()
}
}()
// Run the command and return the pipe
if err := cmd.Start(); err != nil {
+3 -3
View File
@@ -14,7 +14,7 @@ import (
func TestCmdStreamLargeStderr(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
out, err := CmdStream(cmd)
out, err := CmdStream(cmd, nil, nil)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
func TestCmdStreamBad(t *testing.T) {
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
out, err := CmdStream(badCmd)
out, err := CmdStream(badCmd, nil, nil)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) {
func TestCmdStreamGood(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
out, err := CmdStream(cmd)
out, err := CmdStream(cmd, nil, nil)
if err != nil {
t.Fatal(err)
}
+9 -4
View File
@@ -176,6 +176,8 @@ func (b *buildFile) CmdEnv(args string) error {
func (b *buildFile) CmdCmd(args string) error {
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
// If the unmarshal fails, it is not an error, we just use the
// args as a string.
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
cmd = []string{"/bin/sh", "-c", args}
}
@@ -187,6 +189,9 @@ func (b *buildFile) CmdCmd(args string) error {
}
func (b *buildFile) CmdExpose(args string) error {
if strings.Contains(args, ":") {
return fmt.Errorf("EXPOSE cannot be used to bind to a host ip or port")
}
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
@@ -296,7 +301,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
}
// First try to unpack the source as an archive
} else if err := UntarPath(origPath, destPath); err != nil {
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
utils.Debugf("[tar] Not a directory nor a tar archive. Copying as a file. Untar error from %s to %s: %s", origPath, destPath, err)
// If that fails, just copy it as a regular file
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
return err
@@ -332,7 +337,7 @@ func (b *buildFile) CmdAdd(args string) error {
b.config.Image = b.image
// Create the container and start it
container, err := b.runtime.Create(b.config)
container, _, err := b.runtime.Create(b.config)
if err != nil {
return err
}
@@ -367,7 +372,7 @@ func (b *buildFile) run() (string, error) {
b.config.Image = b.image
// Create the container and start it
c, err := b.runtime.Create(b.config)
c, _, err := b.runtime.Create(b.config)
if err != nil {
return "", err
}
@@ -423,7 +428,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
}
}
container, err := b.runtime.Create(b.config)
container, _, err := b.runtime.Create(b.config)
if err != nil {
return err
}
+259 -49
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
)
type ChangeType int
@@ -33,74 +34,283 @@ func (change *Change) String() string {
return fmt.Sprintf("%s %s", kind, change.Path)
}
func Changes(layers []string, rw string) ([]Change, error) {
type FileInfo struct {
parent *FileInfo
name string
stat syscall.Stat_t
children map[string]*FileInfo
}
func (root *FileInfo) LookUp(path string) *FileInfo {
parent := root
if path == "/" {
return root
}
pathElements := strings.Split(path, "/")
for _, elem := range pathElements {
if elem != "" {
child := parent.children[elem]
if child == nil {
return nil
}
parent = child
}
}
return parent
}
func (info *FileInfo) path() string {
if info.parent == nil {
return "/"
}
return filepath.Join(info.parent.path(), info.name)
}
func (info *FileInfo) unlink() {
if info.parent != nil {
delete(info.parent.children, info.name)
}
}
func (info *FileInfo) Remove(path string) bool {
child := info.LookUp(path)
if child != nil {
child.unlink()
return true
}
return false
}
func (info *FileInfo) isDir() bool {
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
}
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
if oldInfo == nil {
// add
change := Change{
Path: info.path(),
Kind: ChangeAdd,
}
*changes = append(*changes, change)
}
// We make a copy so we can modify it to detect additions
// also, we only recurse on the old dir if the new info is a directory
// otherwise any previous delete/change is considered recursive
oldChildren := make(map[string]*FileInfo)
if oldInfo != nil && info.isDir() {
for k, v := range oldInfo.children {
oldChildren[k] = v
}
}
for name, newChild := range info.children {
oldChild, _ := oldChildren[name]
if oldChild != nil {
// change?
oldStat := &oldChild.stat
newStat := &newChild.stat
// Note: We can't compare inode or ctime or blocksize here, because these change
// when copying a file into a container. However, that is not generally a problem
// because any content change will change mtime, and any status change should
// be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting
// back mtime
oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
if oldStat.Mode != newStat.Mode ||
oldStat.Uid != newStat.Uid ||
oldStat.Gid != newStat.Gid ||
oldStat.Rdev != newStat.Rdev ||
// Don't look at size for dirs, its not a good measure of change
(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
oldMtime.Sec != newMtime.Sec ||
oldMtime.Usec != newMtime.Usec {
change := Change{
Path: newChild.path(),
Kind: ChangeModify,
}
*changes = append(*changes, change)
}
// Remove from copy so we can detect deletions
delete(oldChildren, name)
}
newChild.addChanges(oldChild, changes)
}
for _, oldChild := range oldChildren {
// delete
change := Change{
Path: oldChild.path(),
Kind: ChangeDelete,
}
*changes = append(*changes, change)
}
}
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
var changes []Change
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
info.addChanges(oldInfo, &changes)
return changes
}
func newRootFileInfo() *FileInfo {
root := &FileInfo{
name: "/",
children: make(map[string]*FileInfo),
}
return root
}
func applyLayer(root *FileInfo, layer string) error {
err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip root
if layerPath == layer {
return nil
}
// rebase path
relPath, err := filepath.Rel(layer, layerPath)
if err != nil {
return err
}
relPath = filepath.Join("/", relPath)
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched {
if err != nil || !f.IsDir() {
return err
}
return filepath.SkipDir
}
var layerStat syscall.Stat_t
err = syscall.Lstat(layerPath, &layerStat)
if err != nil {
return err
}
file := filepath.Base(relPath)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
deletePath := filepath.Join(filepath.Dir(relPath), originalFile)
root.Remove(deletePath)
} else {
// Added or changed file
existing := root.LookUp(relPath)
if existing != nil {
// Changed file
existing.stat = layerStat
if !existing.isDir() {
// Changed from dir to non-dir, delete all previous files
existing.children = make(map[string]*FileInfo)
}
} else {
// Added file
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
info := &FileInfo{
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
stat: layerStat,
}
parent.children[info.name] = info
}
}
return nil
})
return err
}
func collectFileInfo(sourceDir string) (*FileInfo, error) {
root := newRootFileInfo()
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
path, err = filepath.Rel(rw, path)
relPath, err := filepath.Rel(sourceDir, path)
if err != nil {
return err
}
path = filepath.Join("/", path)
relPath = filepath.Join("/", relPath)
// Skip root
if path == "/" {
if relPath == "/" {
return nil
}
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
info := &FileInfo{
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
}
if err := syscall.Lstat(path, &info.stat); err != nil {
return err
}
change := Change{
Path: path,
}
parent.children[info.name] = info
// Find out what kind of modification happened
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {
// Otherwise, the file was added
change.Kind = ChangeAdd
// ...Unless it already existed in a top layer, in which case, it's a modification
for _, layer := range layers {
stat, err := os.Stat(filepath.Join(layer, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the top layer, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
// Both directories are the same, don't record the change
return nil
}
}
change.Kind = ChangeModify
break
}
}
}
// Record change
changes = append(changes, change)
return nil
})
if err != nil && !os.IsNotExist(err) {
if err != nil {
return nil, err
}
return changes, nil
return root, nil
}
// Compare a directory with an array of layer directories it was based on and
// generate an array of Change objects describing the changes
func ChangesLayers(newDir string, layers []string) ([]Change, error) {
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
oldRoot := newRootFileInfo()
for i := len(layers) - 1; i >= 0; i-- {
layer := layers[i]
if err = applyLayer(oldRoot, layer); err != nil {
return nil, err
}
}
return newRoot.Changes(oldRoot), nil
}
// Compare two directories and generate an array of Change objects describing the changes
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
oldRoot, err := collectFileInfo(oldDir)
if err != nil {
return nil, err
}
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
return newRoot.Changes(oldRoot), nil
}
+111 -41
View File
@@ -23,6 +23,7 @@ import (
"os/signal"
"path/filepath"
"reflect"
"regexp"
"runtime"
"sort"
"strconv"
@@ -465,6 +466,11 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers)
fmt.Fprintf(cli.out, "Images: %d\n", out.Images)
if out.DevmapperDataTotal != 0 {
fmt.Fprintf(cli.out, "Devmapper disk use: Data: %.1f/%.1f Metadata: %.1f/%.1f\n",
float64(out.DevmapperDataUsed)/(1024*1024), float64(out.DevmapperDataTotal)/(1024*1024),
float64(out.DevmapperMetadataUsed)/(1024*1024), float64(out.DevmapperMetadataTotal)/(1024*1024))
}
if out.Debug || os.Getenv("DEBUG") != "" {
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug)
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
@@ -473,6 +479,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion)
fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion)
fmt.Fprintf(cli.out, "Devmapper pool: %s\n", out.DevmapperPool)
}
if len(out.IndexServerAddress) != 0 {
@@ -740,6 +747,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
func (cli *DockerCli) CmdRm(args ...string) error {
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -751,6 +760,9 @@ func (cli *DockerCli) CmdRm(args ...string) error {
if *v {
val.Set("v", "1")
}
if *link {
val.Set("link", "1")
}
for _, name := range cmd.Args() {
_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
if err != nil {
@@ -985,25 +997,19 @@ func (cli *DockerCli) CmdImages(args ...string) error {
out.Tag = "<none>"
}
if !*noTrunc {
out.ID = utils.TruncateID(out.ID)
}
if !*quiet {
fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag)
if *noTrunc {
fmt.Fprintf(w, "%s\t", out.ID)
} else {
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
}
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
if out.VirtualSize > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
} else {
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
}
} else {
if *noTrunc {
fmt.Fprintln(w, out.ID)
} else {
fmt.Fprintln(w, utils.TruncateID(out.ID))
}
fmt.Fprintln(w, out.ID)
}
}
@@ -1017,10 +1023,10 @@ func (cli *DockerCli) CmdImages(args ...string) error {
func displayablePorts(ports []APIPort) string {
result := []string{}
for _, port := range ports {
if port.Type == "tcp" {
result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort))
if port.IP == "" {
result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
} else {
result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type))
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
}
}
sort.Strings(result)
@@ -1073,7 +1079,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
if *size {
fmt.Fprintln(w, "\tSIZE")
} else {
@@ -1082,12 +1088,17 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
for _, out := range outs {
if !*noTrunc {
out.ID = utils.TruncateID(out.ID)
}
if !*quiet {
if *noTrunc {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
if !*noTrunc {
out.Command = utils.Trunc(out.Command, 20)
for i := 0; i < len(out.Names); i++ {
out.Names[i] = utils.Trunc(out.Names[i], 10)
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
if *size {
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
@@ -1098,11 +1109,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
fmt.Fprint(w, "\n")
}
} else {
if *noTrunc {
fmt.Fprintln(w, out.ID)
} else {
fmt.Fprintln(w, utils.TruncateID(out.ID))
}
fmt.Fprintln(w, out.ID)
}
}
@@ -1112,6 +1119,64 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return nil
}
func (cli *DockerCli) CmdLs(args ...string) error {
cmd := Subcmd("ls", "", "List links for containers")
flAll := cmd.Bool("a", false, "Show all links")
if err := cmd.Parse(args); err != nil {
return nil
}
v := url.Values{}
if *flAll {
v.Set("all", "1")
}
body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil)
if err != nil {
return err
}
var links []APILink
if err := json.Unmarshal(body, &links); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tID\tIMAGE")
fmt.Fprintf(w, "\n")
sortLinks(links, func(i, j APILink) bool {
return len(i.Path) < len(j.Path)
})
for _, link := range links {
fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image)
fmt.Fprintf(w, "\n")
}
w.Flush()
return nil
}
func (cli *DockerCli) CmdLink(args ...string) error {
cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 2 {
cmd.Usage()
return nil
}
body := map[string]string{
"currentName": cmd.Arg(0),
"newName": cmd.Arg(1),
}
_, _, err := cli.call("POST", "/containers/link", body)
if err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
flComment := cmd.String("m", "", "Commit message")
@@ -1229,8 +1294,9 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
cmd.Usage()
return nil
}
name := cmd.Arg(0)
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
return err
}
return nil
@@ -1245,8 +1311,8 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
cmd.Usage()
return nil
}
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
name := cmd.Arg(0)
body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
if err != nil {
return err
}
@@ -1326,18 +1392,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
// Ports type - Used to parse multiple -p flags
type ports []int
// ListOpts type
type ListOpts []string
func (opts *ListOpts) String() string {
return fmt.Sprint(*opts)
}
func (opts *ListOpts) Set(value string) error {
*opts = append(*opts, value)
return nil
}
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
type AttachOpts map[string]bool
@@ -1533,7 +1587,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
if config.Tty {
if err := cli.monitorTtySize(runResult.ID); err != nil {
utils.Errorf("Error monitoring TTY size: %s\n", err)
// When running the test suite, there is no terminal, just pipes.
// Discard the error then.
if os.Getenv("TEST") != "1" {
utils.Errorf("Error monitoring TTY size: %s\n", err)
}
}
}
@@ -1642,6 +1700,10 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf)
}
// fixme: refactor client to support redirect
re := regexp.MustCompile("/+")
path = re.ReplaceAllString(path, "/")
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
if err != nil {
return nil, -1, err
@@ -1687,6 +1749,11 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{})
}
// fixme: refactor client to support redirect
re := regexp.MustCompile("/+")
path = re.ReplaceAllString(path, "/")
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
if err != nil {
return err
@@ -1743,6 +1810,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer) error {
// fixme: refactor client to support redirect
re := regexp.MustCompile("/+")
path = re.ReplaceAllString(path, "/")
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
if err != nil {
+18
View File
@@ -0,0 +1,18 @@
package docker
import (
"net"
)
type DaemonConfig struct {
Pidfile string
GraphPath string
ProtoAddresses []string
AutoRestart bool
EnableCors bool
Dns []string
EnableIptables bool
BridgeIface string
DefaultIp net.IP
InterContainerCommunication bool
}
+253 -63
View File
@@ -16,7 +16,6 @@ import (
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
@@ -43,6 +42,7 @@ type Container struct {
ResolvConfPath string
HostnamePath string
HostsPath string
FilesystemType string
cmd *exec.Cmd
stdout *utils.WriteBroadcaster
@@ -58,6 +58,8 @@ type Container struct {
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
// Easier than migrating older container configs :)
VolumesRW map[string]bool
activeLinks map[string]*Link
}
type Config struct {
@@ -70,7 +72,8 @@ type Config struct {
AttachStdin bool
AttachStdout bool
AttachStderr bool
PortSpecs []string
PortSpecs []string // Deprecated - Can be in the format of 8080/tcp
ExposedPorts map[Port]struct{}
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
@@ -90,6 +93,8 @@ type HostConfig struct {
Binds []string
ContainerIDFile string
LxcConf []KeyValuePair
PortBindings map[Port][]PortBinding
Links []string
}
type BindMap struct {
@@ -107,6 +112,34 @@ type KeyValuePair struct {
Value string
}
type PortBinding struct {
HostIp string
HostPort string
}
// 80/tcp
type Port string
func (p Port) Proto() string {
return strings.Split(string(p), "/")[1]
}
func (p Port) Port() string {
return strings.Split(string(p), "/")[0]
}
func (p Port) Int() int {
i, err := parsePort(p.Port())
if err != nil {
panic(err)
}
return i
}
func NewPort(proto, port string) Port {
return Port(fmt.Sprintf("%s/%s", port, proto))
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if os.Getenv("TEST") != "" {
@@ -135,26 +168,32 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
var flPorts ListOpts
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
var flPublish utils.ListOpts
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
var flEnv ListOpts
var flExpose utils.ListOpts
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
var flEnv utils.ListOpts
cmd.Var(&flEnv, "e", "Set environment variables")
var flDns ListOpts
var flDns utils.ListOpts
cmd.Var(&flDns, "dns", "Set custom dns servers")
flVolumes := NewPathOpts()
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
var flVolumesFrom ListOpts
var flVolumesFrom utils.ListOpts
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container")
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
var flLxcOpts ListOpts
var flLxcOpts utils.ListOpts
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
var flLinks utils.ListOpts
cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)")
if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err
}
@@ -220,10 +259,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
hostname = parts[0]
domainname = parts[1]
}
ports, portBindings, err := parsePortSpecs(flPublish)
if err != nil {
return nil, nil, cmd, err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose {
if strings.Contains(e, ":") {
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
}
p := NewPort(splitProtoPort(e))
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
config := &Config{
Hostname: hostname,
Hostname: *flHostname,
Domainname: domainname,
PortSpecs: flPorts,
PortSpecs: nil, // Deprecated
ExposedPorts: ports,
User: *flUser,
Tty: *flTty,
NetworkDisabled: !*flNetwork,
@@ -243,10 +300,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
Privileged: *flPrivileged,
WorkingDir: *flWorkingDir,
}
hostConfig := &HostConfig{
Binds: binds,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
PortBindings: portBindings,
Links: flLinks,
}
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@@ -261,48 +321,55 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
return config, hostConfig, cmd, nil
}
type PortMapping map[string]string
type PortMapping map[string]string // Deprecated
type NetworkSettings struct {
IPAddress string
IPPrefixLen int
Gateway string
Bridge string
PortMapping map[string]PortMapping
PortMapping map[string]PortMapping // Deprecated
Ports map[Port][]PortBinding
}
// returns a more easy to process description of the port mapping defined in the settings
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
var mapping []APIPort
for private, public := range settings.PortMapping["Tcp"] {
pubint, _ := strconv.ParseInt(public, 0, 0)
privint, _ := strconv.ParseInt(private, 0, 0)
mapping = append(mapping, APIPort{
PrivatePort: privint,
PublicPort: pubint,
Type: "tcp",
})
}
for private, public := range settings.PortMapping["Udp"] {
pubint, _ := strconv.ParseInt(public, 0, 0)
privint, _ := strconv.ParseInt(private, 0, 0)
mapping = append(mapping, APIPort{
PrivatePort: privint,
PublicPort: pubint,
Type: "udp",
})
for port, bindings := range settings.Ports {
p, _ := parsePort(port.Port())
if len(bindings) == 0 {
mapping = append(mapping, APIPort{
PublicPort: int64(p),
Type: port.Proto(),
})
continue
}
for _, binding := range bindings {
p, _ := parsePort(port.Port())
h, _ := parsePort(binding.HostPort)
mapping = append(mapping, APIPort{
PrivatePort: int64(p),
PublicPort: int64(h),
Type: port.Proto(),
IP: binding.HostIp,
})
}
}
return mapping
}
// Inject the io.Reader at the given path. Note: do not close the reader
func (container *Container) Inject(file io.Reader, pth string) error {
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
if err := container.EnsureMounted(); err != nil {
return err
}
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil {
return err
}
// FIXME: Handle permissions/already existing dest
dest, err := os.Create(path.Join(container.rwPath(), pth))
dest, err := os.Create(path.Join(container.RootfsPath(), pth))
if err != nil {
return err
}
@@ -590,7 +657,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
if container.runtime.networkManager.disabled {
container.Config.NetworkDisabled = true
} else {
if err := container.allocateNetwork(); err != nil {
if err := container.allocateNetwork(hostConfig); err != nil {
return err
}
}
@@ -753,6 +820,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
}
params := []string{
"lxc-start",
"-n", container.ID,
"-f", container.lxcConfigPath(),
"--",
@@ -780,6 +848,46 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
"-e", "container=lxc",
"-e", "HOSTNAME="+container.Config.Hostname,
)
// Init any links between the parent and children
runtime := container.runtime
children, err := runtime.Children(fmt.Sprintf("/%s", container.ID))
if err != nil {
return err
}
if len(children) > 0 {
container.activeLinks = make(map[string]*Link, len(children))
// If we encounter an error make sure that we rollback any network
// config and ip table changes
rollback := func() {
for _, link := range container.activeLinks {
link.Disable()
}
container.activeLinks = nil
}
for p, child := range children {
link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface)
if err != nil {
rollback()
return err
}
container.activeLinks[link.Alias()] = link
if err := link.Enable(); err != nil {
rollback()
return err
}
for _, envVar := range link.ToEnv() {
params = append(params, "-e", envVar)
}
}
}
if container.Config.WorkingDir != "" {
workingDir := path.Clean(container.Config.WorkingDir)
utils.Debugf("[working dir] working dir is %s", workingDir)
@@ -801,7 +909,21 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
params = append(params, "--", container.Path)
params = append(params, container.Args...)
container.cmd = exec.Command("lxc-start", params...)
if RootIsShared() {
// lxc-start really needs / to be private, or all kinds of stuff break
// What we really want is to clone into a new namespace and then
// mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork
// without exec in go we have to do this horrible shell hack...
shellString :=
"mount --make-rprivate /; exec " +
utils.ShellQuoteArguments(params)
params = []string{
"unshare", "-m", "--", "/bin/sh", "-c", shellString,
}
}
container.cmd = exec.Command(params[0], params[1:]...)
// Setup logging of stdout and stderr to disk
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
@@ -882,7 +1004,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
return utils.NewBufReader(reader), nil
}
func (container *Container) allocateNetwork() error {
func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
if container.Config.NetworkDisabled {
return nil
}
@@ -909,36 +1031,59 @@ func (container *Container) allocateNetwork() error {
}
}
var portSpecs []string
if !container.State.Ghost {
portSpecs = container.Config.PortSpecs
} else {
for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] {
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend))
if container.Config.PortSpecs != nil {
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
if err := migratePortMappings(container.Config); err != nil {
return err
}
for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] {
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend))
container.Config.PortSpecs = nil
}
portSpecs := make(map[Port]struct{})
bindings := make(map[Port][]PortBinding)
if !container.State.Ghost {
if container.Config.ExposedPorts != nil {
portSpecs = container.Config.ExposedPorts
}
if hostConfig.PortBindings != nil {
bindings = hostConfig.PortBindings
}
} else {
if container.NetworkSettings.Ports != nil {
for port, binding := range container.NetworkSettings.Ports {
portSpecs[port] = struct{}{}
bindings[port] = binding
}
}
}
container.NetworkSettings.PortMapping = make(map[string]PortMapping)
container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping)
container.NetworkSettings.PortMapping["Udp"] = make(PortMapping)
for _, spec := range portSpecs {
nat, err := iface.AllocatePort(spec)
if err != nil {
iface.Release()
return err
container.NetworkSettings.PortMapping = nil
for port := range portSpecs {
binding := bindings[port]
for i := 0; i < len(binding); i++ {
b := binding[i]
nat, err := iface.AllocatePort(port, b)
if err != nil {
iface.Release()
return err
}
utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort)
binding[i] = nat.Binding
}
proto := strings.Title(nat.Proto)
backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend)
container.NetworkSettings.PortMapping[proto][backend] = frontend
bindings[port] = binding
}
container.SaveHostConfig(hostConfig)
container.NetworkSettings.Ports = bindings
container.network = iface
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.Gateway = iface.Gateway.String()
return nil
}
@@ -951,7 +1096,7 @@ func (container *Container) releaseNetwork() {
container.NetworkSettings = &NetworkSettings{}
}
// FIXME: replace this with a control socket within docker-init
// FIXME: replace this with a control socket within dockerinit
func (container *Container) waitLxc() error {
for {
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
@@ -973,7 +1118,8 @@ func (container *Container) monitor(hostConfig *HostConfig) {
if container.cmd == nil {
utils.Debugf("monitor: waiting for container %s using waitLxc", container.ID)
if err := container.waitLxc(); err != nil {
utils.Errorf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ID, err)
// Discard the error as any signals or non 0 returns will generate an error
utils.Debugf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ShortID(), err)
}
} else {
utils.Debugf("monitor: waiting for container %s using cmd.Wait", container.ID)
@@ -1021,6 +1167,14 @@ func (container *Container) monitor(hostConfig *HostConfig) {
func (container *Container) cleanup() {
container.releaseNetwork()
// Disable all active links
if container.activeLinks != nil {
for _, link := range container.activeLinks {
link.Disable()
}
}
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil {
utils.Errorf("%s: Error close stdin: %s", container.ID, err)
@@ -1132,7 +1286,15 @@ func (container *Container) Resize(h, w int) error {
}
func (container *Container) ExportRw() (Archive, error) {
return Tar(container.rwPath(), Uncompressed)
if err := container.EnsureMounted(); err != nil {
return nil, err
}
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
}
func (container *Container) RwChecksum() (string, error) {
@@ -1174,20 +1336,33 @@ func (container *Container) EnsureMounted() error {
return container.Mount()
}
func (container *Container) EnsureUnmounted() error {
if mounted, err := container.Mounted(); err != nil {
return err
} else if !mounted {
return nil
}
return container.Unmount()
}
func (container *Container) Mount() error {
image, err := container.GetImage()
if err != nil {
return err
}
return image.Mount(container.RootfsPath(), container.rwPath())
return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
}
func (container *Container) Changes() ([]Change, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.Changes(container.rwPath())
return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
}
func (container *Container) GetImage() (*Image, error) {
@@ -1198,11 +1373,20 @@ func (container *Container) GetImage() (*Image, error) {
}
func (container *Container) Mounted() (bool, error) {
return Mounted(container.RootfsPath())
image, err := container.GetImage()
if err != nil {
return false, err
}
return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath())
}
func (container *Container) Unmount() error {
return Unmount(container.RootfsPath())
image, err := container.GetImage()
if err != nil {
return err
}
err = image.Unmount(container.runtime, container.RootfsPath(), container.ID)
return err
}
// ShortID returns a shorthand version of the container's id for convenience.
@@ -1290,5 +1474,11 @@ func (container *Container) Copy(resource string) (Archive, error) {
filter = []string{path.Base(basePath)}
basePath = path.Dir(basePath)
}
return TarFilter(basePath, Uncompressed, filter)
return TarFilter(basePath, Uncompressed, filter, true, nil)
}
// Returns true if the container exposes a certain port
func (container *Container) Exposes(p Port) bool {
_, exists := container.Config.ExposedPorts[p]
return exists
}
+56 -36
View File
@@ -18,7 +18,7 @@ import (
func TestIDFormat(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container1, err := runtime.Create(
container1, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
@@ -388,7 +388,7 @@ func TestRun(t *testing.T) {
func TestOutput(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
@@ -407,11 +407,32 @@ func TestOutput(t *testing.T) {
}
}
func TestContainerNetwork(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"ping", "-c", "1", "127.0.0.1"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Run(); err != nil {
t.Fatal(err)
}
if container.State.ExitCode != 0 {
t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode)
}
}
func TestKillDifferentUser(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
@@ -471,7 +492,7 @@ func TestCreateVolume(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c, err := runtime.Create(config)
c, _, err := runtime.Create(config)
if err != nil {
t.Fatal(err)
}
@@ -486,7 +507,7 @@ func TestCreateVolume(t *testing.T) {
func TestKill(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
@@ -530,7 +551,7 @@ func TestExitCode(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
trueContainer, err := runtime.Create(&Config{
trueContainer, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""},
})
@@ -545,7 +566,7 @@ func TestExitCode(t *testing.T) {
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
}
falseContainer, err := runtime.Create(&Config{
falseContainer, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""},
})
@@ -564,7 +585,7 @@ func TestExitCode(t *testing.T) {
func TestRestart(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
@@ -594,7 +615,7 @@ func TestRestart(t *testing.T) {
func TestRestartStdin(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
@@ -672,7 +693,7 @@ func TestUser(t *testing.T) {
defer nuke(runtime)
// Default user must be root
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
},
@@ -690,7 +711,7 @@ func TestUser(t *testing.T) {
}
// Set a username
container, err = runtime.Create(&Config{
container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
@@ -710,7 +731,7 @@ func TestUser(t *testing.T) {
}
// Set a UID
container, err = runtime.Create(&Config{
container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
@@ -730,7 +751,7 @@ func TestUser(t *testing.T) {
}
// Set a different user by uid
container, err = runtime.Create(&Config{
container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
@@ -752,7 +773,7 @@ func TestUser(t *testing.T) {
}
// Set a different user by username
container, err = runtime.Create(&Config{
container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
@@ -772,7 +793,7 @@ func TestUser(t *testing.T) {
}
// Test an wrong username
container, err = runtime.Create(&Config{
container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
@@ -793,7 +814,7 @@ func TestMultipleContainers(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container1, err := runtime.Create(&Config{
container1, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
@@ -803,7 +824,7 @@ func TestMultipleContainers(t *testing.T) {
}
defer runtime.Destroy(container1)
container2, err := runtime.Create(&Config{
container2, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
@@ -847,7 +868,7 @@ func TestMultipleContainers(t *testing.T) {
func TestStdin(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
@@ -892,7 +913,7 @@ func TestStdin(t *testing.T) {
func TestTty(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
@@ -937,7 +958,7 @@ func TestTty(t *testing.T) {
func TestEnv(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"env"},
},
@@ -986,7 +1007,7 @@ func TestEnv(t *testing.T) {
func TestEntrypoint(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo"},
@@ -1009,7 +1030,7 @@ func TestEntrypoint(t *testing.T) {
func TestEntrypointNoCmd(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo", "foobar"},
@@ -1060,7 +1081,7 @@ func TestLXCConfig(t *testing.T) {
cpuMin := 100
cpuMax := 10000
cpu := cpuMin + rand.Intn(cpuMax-cpuMin)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true"},
@@ -1072,7 +1093,6 @@ func TestLXCConfig(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
container.generateLXCConfig(nil)
grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar")
grepFile(t, container.lxcConfigPath(),
@@ -1084,7 +1104,7 @@ func TestLXCConfig(t *testing.T) {
func TestCustomLxcConfig(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true"},
@@ -1115,7 +1135,7 @@ func BenchmarkRunSequencial(b *testing.B) {
runtime := mkRuntime(b)
defer nuke(runtime)
for i := 0; i < b.N; i++ {
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
@@ -1147,7 +1167,7 @@ func BenchmarkRunParallel(b *testing.B) {
complete := make(chan error)
tasks = append(tasks, complete)
go func(i int, complete chan error) {
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
@@ -1297,7 +1317,7 @@ func TestBindMounts(t *testing.T) {
func TestVolumesFromReadonlyMount(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"},
@@ -1316,7 +1336,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
t.Fail()
}
container2, err := runtime.Create(
container2, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"},
@@ -1352,7 +1372,7 @@ func TestRestartWithVolumes(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
Volumes: map[string]struct{}{"/test": {}},
@@ -1395,7 +1415,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
@@ -1422,7 +1442,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
t.Fail()
}
container2, err := runtime.Create(
container2, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/test/foo"},
@@ -1463,7 +1483,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c, err := runtime.Create(config)
c, _, err := runtime.Create(config)
if err != nil {
t.Fatal(err)
}
@@ -1529,7 +1549,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
@@ -1556,7 +1576,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
t.Fail()
}
container2, err := runtime.Create(
container2, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"},
@@ -1577,7 +1597,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
t.Fatal(err)
}
container3, err := runtime.Create(
container3, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"},
+850
View File
@@ -0,0 +1,850 @@
package devmapper
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
)
var (
DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
)
type DevInfo struct {
Hash string `json:"-"`
DeviceId int `json:"device_id"`
Size uint64 `json:"size"`
TransactionId uint64 `json:"transaction_id"`
Initialized bool `json:"initialized"`
devices *DeviceSet `json:"-"`
}
type MetaData struct {
Devices map[string]*DevInfo `json:devices`
}
type DeviceSet struct {
MetaData
sync.Mutex
initialized bool
root string
devicePrefix string
TransactionId uint64
NewTransactionId uint64
nextFreeDevice int
activeMounts map[string]int
}
type DiskUsage struct {
Used uint64
Total uint64
}
type Status struct {
PoolName string
DataLoopback string
MetadataLoopback string
Data DiskUsage
Metadata DiskUsage
}
func getDevName(name string) string {
return "/dev/mapper/" + name
}
func (info *DevInfo) Name() string {
hash := info.Hash
if hash == "" {
hash = "base"
}
return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash)
}
func (info *DevInfo) DevName() string {
return getDevName(info.Name())
}
func (devices *DeviceSet) loopbackDir() string {
return path.Join(devices.root, "devicemapper")
}
func (devices *DeviceSet) jsonFile() string {
return path.Join(devices.loopbackDir(), "json")
}
func (devices *DeviceSet) getPoolName() string {
return devices.devicePrefix + "-pool"
}
func (devices *DeviceSet) getPoolDevName() string {
return getDevName(devices.getPoolName())
}
func (devices *DeviceSet) hasImage(name string) bool {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
_, err := os.Stat(filename)
return err == nil
}
// ensureImage creates a sparse file of <size> bytes at the path
// <root>/devicemapper/<name>.
// If the file already exists, it does nothing.
// Either way it returns the full path.
func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) {
return "", err
}
if _, err := os.Stat(filename); err != nil {
if !os.IsNotExist(err) {
return "", err
}
utils.Debugf("Creating loopback file %s for device-manage use", filename)
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return "", err
}
if err = file.Truncate(size); err != nil {
return "", err
}
}
return filename, nil
}
func (devices *DeviceSet) allocateDeviceId() int {
// TODO: Add smarter reuse of deleted devices
id := devices.nextFreeDevice
devices.nextFreeDevice = devices.nextFreeDevice + 1
return id
}
func (devices *DeviceSet) allocateTransactionId() uint64 {
devices.NewTransactionId = devices.NewTransactionId + 1
return devices.NewTransactionId
}
func (devices *DeviceSet) saveMetadata() error {
jsonData, err := json.Marshal(devices.MetaData)
if err != nil {
return fmt.Errorf("Error encoding metaadata to json: %s", err)
}
tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json")
if err != nil {
return fmt.Errorf("Error creating metadata file: %s", err)
}
n, err := tmpFile.Write(jsonData)
if err != nil {
return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err)
}
if n < len(jsonData) {
return io.ErrShortWrite
}
if err := tmpFile.Sync(); err != nil {
return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
}
if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
return fmt.Errorf("Error committing metadata file", err)
}
if devices.NewTransactionId != devices.TransactionId {
if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
return fmt.Errorf("Error setting devmapper transition ID: %s", err)
}
devices.TransactionId = devices.NewTransactionId
}
return nil
}
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
utils.Debugf("registerDevice(%v, %v)", id, hash)
info := &DevInfo{
Hash: hash,
DeviceId: id,
Size: size,
TransactionId: devices.allocateTransactionId(),
Initialized: false,
devices: devices,
}
devices.Devices[hash] = info
if err := devices.saveMetadata(); err != nil {
// Try to remove unused device
delete(devices.Devices, hash)
return nil, err
}
return info, nil
}
func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
utils.Debugf("activateDeviceIfNeeded(%v)", hash)
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
return nil
}
return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
}
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
devname := info.DevName()
err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
if err != nil {
err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
}
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) loadMetaData() error {
utils.Debugf("loadMetadata()")
defer utils.Debugf("loadMetadata END")
_, _, _, params, err := getStatus(devices.getPoolName())
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.NewTransactionId = devices.TransactionId
jsonData, err := ioutil.ReadFile(devices.jsonFile())
if err != nil && !os.IsNotExist(err) {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.MetaData.Devices = make(map[string]*DevInfo)
if jsonData != nil {
if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
for hash, d := range devices.Devices {
d.Hash = hash
d.devices = devices
if d.DeviceId >= devices.nextFreeDevice {
devices.nextFreeDevice = d.DeviceId + 1
}
// If the transaction id is larger than the actual one we lost the device due to some crash
if d.TransactionId > devices.TransactionId {
utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId)
delete(devices.Devices, hash)
}
}
return nil
}
func (devices *DeviceSet) setupBaseImage() error {
oldInfo := devices.Devices[""]
if oldInfo != nil && oldInfo.Initialized {
return nil
}
if oldInfo != nil && !oldInfo.Initialized {
utils.Debugf("Removing uninitialized base image")
if err := devices.removeDevice(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
utils.Debugf("Initializing base device-manager snapshot")
id := devices.allocateDeviceId()
// Create initial device
if err := createDevice(devices.getPoolDevName(), id); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
if err != nil {
_ = deleteDevice(devices.getPoolDevName(), id)
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Creating filesystem on base device-manager snapshot")
if err = devices.activateDeviceIfNeeded(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.createFilesystem(info); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
info.Initialized = true
if err = devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func setCloseOnExec(name string) {
fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
if fileInfos != nil {
for _, i := range fileInfos {
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
if link == name {
fd, err := strconv.Atoi(i.Name())
if err == nil {
syscall.CloseOnExec(fd)
}
}
}
}
}
func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
if level >= 7 {
return // Ignore _LOG_DEBUG
}
utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
func (devices *DeviceSet) initDevmapper() error {
logInit(devices)
// Make sure the sparse images exist in <root>/devicemapper/data and
// <root>/devicemapper/metadata
createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata")
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (data): %s\n", err)
return err
}
metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (metadata): %s\n", err)
return err
}
// Set the device prefix from the device id and inode of the docker root dir
st, err := os.Stat(devices.root)
if err != nil {
return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
}
sysSt := st.Sys().(*syscall.Stat_t)
// "reg-" stands for "regular file".
// In the future we might use "dev-" for "device file", etc.
// docker-maj,min[-inode] stands for:
// - Managed by docker
// - The target of this device is at major <maj> and minor <min>
// - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself.
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
utils.Debugf("Generated prefix: %s", devices.devicePrefix)
// Check for the existence of the device <prefix>-pool
utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
info, err := getInfo(devices.getPoolName())
if info == nil {
utils.Debugf("Error device getInfo: %s", err)
return err
}
// It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files
// that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files,
// so we add this badhack to make sure it closes itself
setCloseOnExec("/dev/mapper/control")
// If the pool doesn't exist, create it
if info.Exists == 0 {
utils.Debugf("Pool doesn't exist. Creating it.")
dataFile, err := AttachLoopDevice(data)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer dataFile.Close()
metadataFile, err := AttachLoopDevice(metadata)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer metadataFile.Close()
if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// If we didn't just create the data or metadata image, we need to
// load the metadata from the existing file.
if !createdLoopback {
if err = devices.loadMetaData(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// Setup the base image
if err := devices.setupBaseImage(); err != nil {
utils.Debugf("Error device setupBaseImage: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("Error init: %s\n", err)
return err
}
if devices.Devices[hash] != nil {
return fmt.Errorf("hash %s already exists", hash)
}
baseInfo := devices.Devices[baseHash]
if baseInfo == nil {
return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash)
}
deviceId := devices.allocateDeviceId()
if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
utils.Debugf("Error creating snap device: %s\n", err)
return err
}
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
deleteDevice(devices.getPoolDevName(), deviceId)
utils.Debugf("Error registering device: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) removeDevice(hash string) error {
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("hash %s doesn't exists", hash)
}
devinfo, _ := getInfo(info.Name())
if devinfo != nil && devinfo.Exists != 0 {
if err := removeDevice(info.Name()); err != nil {
utils.Debugf("Error removing device: %s\n", err)
return err
}
}
if info.Initialized {
info.Initialized = false
if err := devices.saveMetadata(); err != nil {
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
}
if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
utils.Debugf("Error deleting device: %s\n", err)
return err
}
devices.allocateTransactionId()
delete(devices.Devices, info.Hash)
if err := devices.saveMetadata(); err != nil {
devices.Devices[info.Hash] = info
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) RemoveDevice(hash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return devices.removeDevice(hash)
}
func (devices *DeviceSet) deactivateDevice(hash string) error {
utils.Debugf("[devmapper] deactivateDevice(%s)", hash)
defer utils.Debugf("[devmapper] deactivateDevice END")
var devname string
// FIXME: shouldn't we just register the pool into devices?
devname, err := devices.byHash(hash)
if err != nil {
return err
}
devinfo, err := getInfo(devname)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if devinfo.Exists != 0 {
if err := removeDevice(devname); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.waitRemove(hash); err != nil {
return err
}
}
return nil
}
// waitRemove blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is removed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitRemove(hash string) error {
utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash)
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
// If there is an error we assume the device doesn't exist.
// The error might actually be something else, but we can't differentiate.
return nil
}
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
if devinfo.Exists == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to be removed", devname)
}
return nil
}
// waitClose blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is closed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitClose(hash string) error {
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
return err
}
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
if devinfo.OpenCount == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to close", devname)
}
return nil
}
// byHash is a hack to allow looking up the deviceset's pool by the hash "pool".
// FIXME: it seems probably cleaner to register the pool in devices.Devices,
// but I am afraid of arcane implications deep in the devicemapper code,
// so this will do.
func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
if hash == "pool" {
return devices.getPoolDevName(), nil
}
info := devices.Devices[hash]
if info == nil {
return "", fmt.Errorf("hash %s doesn't exists", hash)
}
return info.Name(), nil
}
func (devices *DeviceSet) Shutdown() error {
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
devices.Lock()
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
defer devices.Unlock()
if !devices.initialized {
return nil
}
for path, count := range devices.activeMounts {
for i := count; i > 0; i-- {
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
}
}
delete(devices.activeMounts, path)
}
for _, d := range devices.Devices {
if err := devices.waitClose(d.Hash); err != nil {
utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err)
}
if err := devices.deactivateDevice(d.Hash); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err)
}
}
pool := devices.getPoolDevName()
if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 {
if err := devices.deactivateDevice("pool"); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err)
}
}
return nil
}
func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return fmt.Errorf("Error initializing devmapper: %s", err)
}
if err := devices.activateDeviceIfNeeded(hash); err != nil {
return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
}
info := devices.Devices[hash]
var flags uintptr = syscall.MS_MGC_VAL
if readOnly {
flags = flags | syscall.MS_RDONLY
}
err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
if err != nil && err == syscall.EINVAL {
err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
}
if err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
}
count := devices.activeMounts[path]
devices.activeMounts[path] = count + 1
return nil
}
func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error {
utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path)
defer utils.Debugf("[devmapper] UnmountDevice END")
devices.Lock()
defer devices.Unlock()
utils.Debugf("[devmapper] Unmount(%s)", path)
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("[devmapper] Unmount done")
// Wait for the unmount to be effective,
// by watching the value of Info.OpenCount for the device
if err := devices.waitClose(hash); err != nil {
return err
}
if count := devices.activeMounts[path]; count > 1 {
devices.activeMounts[path] = count - 1
} else {
delete(devices.activeMounts, path)
}
if deactivate {
devices.deactivateDevice(hash)
}
return nil
}
func (devices *DeviceSet) HasDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
return devices.Devices[hash] != nil
}
func (devices *DeviceSet) HasInitializedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
info := devices.Devices[hash]
return info != nil && info.Initialized
}
func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
info := devices.Devices[hash]
if info == nil {
return false
}
devinfo, _ := getInfo(info.Name())
return devinfo != nil && devinfo.Exists != 0
}
func (devices *DeviceSet) SetInitialized(hash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
info.Initialized = true
if err := devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) Status() *Status {
devices.Lock()
defer devices.Unlock()
status := &Status{}
if err := devices.ensureInit(); err != nil {
return status
}
status.PoolName = devices.getPoolName()
status.DataLoopback = path.Join(devices.loopbackDir(), "data")
status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata")
_, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName())
if err == nil {
var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64
if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil {
// Convert from blocks to bytes
blockSizeInSectors := totalSizeInSectors / dataTotal
status.Data.Used = dataUsed * blockSizeInSectors * 512
status.Data.Total = dataTotal * blockSizeInSectors * 512
// metadata blocks are always 4k
status.Metadata.Used = metadataUsed * 4096
status.Metadata.Total = metadataTotal * 4096
}
}
return status
}
func (devices *DeviceSet) ensureInit() error {
if !devices.initialized {
devices.initialized = true
if err := devices.initDevmapper(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
return nil
}
func NewDeviceSet(root string) *DeviceSet {
SetDevDir("/dev")
return &DeviceSet{
initialized: false,
root: root,
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
activeMounts: make(map[string]int),
}
}
+709
View File
@@ -0,0 +1,709 @@
package devmapper
/*
#cgo LDFLAGS: -L. -ldevmapper
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libdevmapper.h>
#include <linux/loop.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <errno.h>
#ifndef LOOP_CTL_GET_FREE
#define LOOP_CTL_GET_FREE 0x4C82
#endif
// FIXME: this could easily be rewritten in go
char* attach_loop_device(const char *filename, int *loop_fd_out)
{
struct loop_info64 loopinfo = {0};
struct stat st;
char buf[64];
int i, loop_fd, fd, start_index;
char* loopname;
*loop_fd_out = -1;
start_index = 0;
fd = open("/dev/loop-control", O_RDONLY);
if (fd >= 0) {
start_index = ioctl(fd, LOOP_CTL_GET_FREE);
close(fd);
if (start_index < 0)
start_index = 0;
}
fd = open(filename, O_RDWR);
if (fd < 0) {
perror("open");
return NULL;
}
loop_fd = -1;
for (i = start_index ; loop_fd < 0 ; i++ ) {
if (sprintf(buf, "/dev/loop%d", i) < 0) {
close(fd);
return NULL;
}
if (stat(buf, &st)) {
if (!S_ISBLK(st.st_mode)) {
fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf);
} else if (errno == ENOENT) {
fprintf(stderr, "[error] There are no more loopback device available.\n");
} else {
fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno);
}
close(fd);
return NULL;
}
loop_fd = open(buf, O_RDWR);
if (loop_fd < 0 && errno == ENOENT) {
fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf);
close(fd);
return NULL;
} else if (loop_fd < 0) {
fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno);
continue;
}
if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) {
int errsv = errno;
close(loop_fd);
loop_fd = -1;
if (errsv != EBUSY) {
close(fd);
fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv));
return NULL;
}
continue;
}
close(fd);
strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE);
loopinfo.lo_offset = 0;
loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR;
if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) {
perror("ioctl LOOP_SET_STATUS64");
if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
perror("ioctl LOOP_CLR_FD");
}
close(loop_fd);
fprintf (stderr, "cannot set up loopback device info");
return (NULL);
}
loopname = strdup(buf);
if (loopname == NULL) {
close(loop_fd);
return (NULL);
}
*loop_fd_out = loop_fd;
return (loopname);
}
return (NULL);
}
static int64_t get_block_size(int fd)
{
uint64_t size;
if (ioctl(fd, BLKGETSIZE64, &size) == -1)
return -1;
return ((int64_t)size);
}
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
static void
log_cb(int level, const char *file, int line,
int dm_errno_or_class, const char *f, ...)
{
char buffer[256];
va_list ap;
va_start(ap, f);
vsnprintf(buffer, 256, f, ap);
va_end(ap);
DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}
static void
log_with_errno_init ()
{
dm_log_with_errno_init(log_cb);
}
*/
import "C"
import (
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"runtime"
"syscall"
"unsafe"
)
type DevmapperLogger interface {
log(level int, file string, line int, dmError int, message string)
}
const (
DeviceCreate TaskType = iota
DeviceReload
DeviceRemove
DeviceRemoveAll
DeviceSuspend
DeviceResume
DeviceInfo
DeviceDeps
DeviceRename
DeviceVersion
DeviceStatus
DeviceTable
DeviceWaitevent
DeviceList
DeviceClear
DeviceMknodes
DeviceListVersions
DeviceTargetMsg
DeviceSetGeometry
)
const (
AddNodeOnResume AddNodeType = iota
AddNodeOnCreate
)
var (
ErrTaskRun = errors.New("dm_task_run failed")
ErrTaskSetName = errors.New("dm_task_set_name failed")
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
ErrTaskSetRO = errors.New("dm_task_set_ro failed")
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
ErrGetBlockSize = errors.New("Can't get block size")
ErrUdevWait = errors.New("wait on udev cookie failed")
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
ErrRunRemoveDevice = errors.New("running removeDevice failed")
)
type (
Task struct {
unmanaged *C.struct_dm_task
}
Info struct {
Exists int
Suspended int
LiveTable int
InactiveTable int
OpenCount int32
EventNr uint32
Major uint32
Minor uint32
ReadOnly int
TargetCount int32
}
TaskType int
AddNodeType int
)
func (t *Task) destroy() {
if t != nil {
C.dm_task_destroy(t.unmanaged)
runtime.SetFinalizer(t, nil)
}
}
func TaskCreate(tasktype TaskType) *Task {
c_task := C.dm_task_create(C.int(tasktype))
if c_task == nil {
return nil
}
task := &Task{unmanaged: c_task}
runtime.SetFinalizer(task, (*Task).destroy)
return task
}
func (t *Task) Run() error {
if res := C.dm_task_run(t.unmanaged); res != 1 {
return ErrTaskRun
}
return nil
}
func (t *Task) SetName(name string) error {
c_name := C.CString(name)
defer free(c_name)
if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 {
return ErrTaskSetName
}
return nil
}
func (t *Task) SetMessage(message string) error {
c_message := C.CString(message)
defer free(c_message)
if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 {
return ErrTaskSetMessage
}
return nil
}
func (t *Task) SetSector(sector uint64) error {
if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetCookie(cookie *uint32, flags uint16) error {
c_cookie := C.uint32_t(*cookie)
if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 {
return ErrTaskSetAddNode
}
*cookie = uint32(c_cookie)
return nil
}
func (t *Task) SetAddNode(add_node AddNodeType) error {
if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t(add_node)); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetRo() error {
if res := C.dm_task_set_ro(t.unmanaged); res != 1 {
return ErrTaskSetRO
}
return nil
}
func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error {
c_ttype := C.CString(ttype)
defer free(c_ttype)
c_params := C.CString(params)
defer free(c_params)
if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 {
return ErrTaskAddTarget
}
return nil
}
func (t *Task) GetDriverVersion() (string, error) {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 {
return "", ErrGetDriverVersion
}
return C.GoString(buffer), nil
}
func (t *Task) GetInfo() (*Info, error) {
c_info := C.struct_dm_info{}
if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 {
return nil, ErrGetDriverVersion
}
return &Info{
Exists: int(c_info.exists),
Suspended: int(c_info.suspended),
LiveTable: int(c_info.live_table),
InactiveTable: int(c_info.inactive_table),
OpenCount: int32(c_info.open_count),
EventNr: uint32(c_info.event_nr),
Major: uint32(c_info.major),
Minor: uint32(c_info.minor),
ReadOnly: int(c_info.read_only),
TargetCount: int32(c_info.target_count),
}, nil
}
func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) {
var (
c_start, c_length C.uint64_t
c_target_type, c_params *C.char
)
nextp := C.dm_get_next_target(t.unmanaged, unsafe.Pointer(next), &c_start, &c_length, &c_target_type, &c_params)
return uintptr(nextp), uint64(c_start), uint64(c_length), C.GoString(c_target_type), C.GoString(c_params)
}
func AttachLoopDevice(filename string) (*os.File, error) {
c_filename := C.CString(filename)
defer free(c_filename)
var fd C.int
res := C.attach_loop_device(c_filename, &fd)
if res == nil {
if os.Getenv("DEBUG") != "" {
C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, %d)", filename, int(fd))))
}
return nil, ErrAttachLoopbackDevice
}
defer free(res)
return os.NewFile(uintptr(fd), C.GoString(res)), nil
}
func getBlockSize(fd uintptr) int {
var size uint64
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
utils.Debugf("Error ioctl: %s", err)
return -1
}
return int(size)
}
func GetBlockDeviceSize(file *os.File) (uint64, error) {
if size := C.get_block_size(C.int(file.Fd())); size == -1 {
return 0, ErrGetBlockSize
} else {
return uint64(size), nil
}
}
func UdevWait(cookie uint32) error {
if res := C.dm_udev_wait(C.uint32_t(cookie)); res != 1 {
utils.Debugf("Failed to wait on udev cookie %d", cookie)
return ErrUdevWait
}
return nil
}
func LogInitVerbose(level int) {
C.dm_log_init_verbose(C.int(level))
}
var dmLogger DevmapperLogger = nil
func logInit(logger DevmapperLogger) {
dmLogger = logger
C.log_with_errno_init()
}
func SetDevDir(dir string) error {
c_dir := C.CString(dir)
defer free(c_dir)
if res := C.dm_set_dev_dir(c_dir); res != 1 {
utils.Debugf("Error dm_set_dev_dir")
return ErrSetDevDir
}
return nil
}
func GetLibraryVersion() (string, error) {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
if res := C.dm_get_library_version(buffer, 128); res != 1 {
return "", ErrGetLibraryVersion
}
return C.GoString(buffer), nil
}
// Useful helper for cleanup
func RemoveDevice(name string) error {
task := TaskCreate(DeviceRemove)
if task == nil {
return ErrCreateRemoveTask
}
if err := task.SetName(name); err != nil {
utils.Debugf("Can't set task name %s", name)
return err
}
if err := task.Run(); err != nil {
return ErrRunRemoveDevice
}
return nil
}
func free(p *C.char) {
C.free(unsafe.Pointer(p))
}
// This is the programmatic example of "dmsetup create"
func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
task, err := createTask(DeviceCreate, poolName)
if task == nil {
return err
}
size, err := GetBlockDeviceSize(dataFile)
if err != nil {
return fmt.Errorf("Can't get data size")
}
params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
return fmt.Errorf("Can't add target")
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate")
}
UdevWait(cookie)
return nil
}
func createTask(t TaskType, name string) (*Task, error) {
task := TaskCreate(t)
if task == nil {
return nil, fmt.Errorf("Can't create task of type %d", int(t))
}
if err := task.SetName(name); err != nil {
return nil, fmt.Errorf("Can't set task name %s", name)
}
return task, nil
}
func getInfo(name string) (*Info, error) {
task, err := createTask(DeviceInfo, name)
if task == nil {
return nil, err
}
if err := task.Run(); err != nil {
return nil, err
}
return task.GetInfo()
}
func getStatus(name string) (uint64, uint64, string, string, error) {
task, err := createTask(DeviceStatus, name)
if task == nil {
utils.Debugf("getStatus: Error createTask: %s", err)
return 0, 0, "", "", err
}
if err := task.Run(); err != nil {
utils.Debugf("getStatus: Error Run: %s", err)
return 0, 0, "", "", err
}
devinfo, err := task.GetInfo()
if err != nil {
utils.Debugf("getStatus: Error GetInfo: %s", err)
return 0, 0, "", "", err
}
if devinfo.Exists == 0 {
utils.Debugf("getStatus: Non existing device %s", name)
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
}
_, start, length, target_type, params := task.GetNextTarget(0)
return start, length, target_type, params, nil
}
func setTransactionId(poolName string, oldId uint64, newId uint64) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running setTransactionId")
}
return nil
}
func suspendDevice(name string) error {
task, err := createTask(DeviceSuspend, name)
if task == nil {
return err
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
return nil
}
func resumeDevice(name string) error {
task, err := createTask(DeviceResume, name)
if task == nil {
return err
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
UdevWait(cookie)
return nil
}
func createDevice(poolName string, deviceId int) error {
utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running createDevice")
}
return nil
}
func deleteDevice(poolName string, deviceId int) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running deleteDevice")
}
return nil
}
func removeDevice(name string) error {
utils.Debugf("[devmapper] removeDevice START")
defer utils.Debugf("[devmapper] removeDevice END")
task, err := createTask(DeviceRemove, name)
if task == nil {
return err
}
if err = task.Run(); err != nil {
return fmt.Errorf("Error running removeDevice")
}
return nil
}
func activateDevice(poolName string, name string, deviceId int, size uint64) error {
task, err := createTask(DeviceCreate, name)
if task == nil {
return err
}
params := fmt.Sprintf("%s %d", poolName, deviceId)
if err := task.AddTarget(0, size/512, "thin", params); err != nil {
return fmt.Errorf("Can't add target")
}
if err := task.SetAddNode(AddNodeOnCreate); err != nil {
return fmt.Errorf("Can't add node")
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate")
}
UdevWait(cookie)
return nil
}
func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
devinfo, _ := getInfo(baseName)
doSuspend := devinfo != nil && devinfo.Exists != 0
if doSuspend {
if err := suspendDevice(baseName); err != nil {
return err
}
}
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
if doSuspend {
resumeDevice(baseName)
}
return err
}
if err := task.SetSector(0); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Error running DeviceCreate")
}
if doSuspend {
if err := resumeDevice(baseName); err != nil {
return err
}
}
return nil
}
+13
View File
@@ -0,0 +1,13 @@
package devmapper
import "C"
// Due to the way cgo works this has to be in a separate file, as devmapper.go has
// definitions in the cgo block, which is incompatible with using "//export"
//export DevmapperLogCallback
func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
if dmLogger != nil {
dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message))
}
}
@@ -0,0 +1,62 @@
package main
import (
"fmt"
"github.com/dotcloud/docker/devmapper"
"os"
)
func usage() {
fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
os.Exit(1)
}
func main() {
devices := devmapper.NewDeviceSet("/var/lib/docker")
if len(os.Args) < 2 {
usage()
}
cmd := os.Args[1]
if cmd == "snap" {
if len(os.Args) < 4 {
usage()
}
err := devices.AddDevice(os.Args[2], os.Args[3])
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
} else if cmd == "remove" {
if len(os.Args) < 3 {
usage()
}
err := devices.RemoveDevice(os.Args[2])
if err != nil {
fmt.Println("Can't remove device: ", err)
os.Exit(1)
}
} else if cmd == "mount" {
if len(os.Args) < 4 {
usage()
}
err := devices.MountDevice(os.Args[2], os.Args[3])
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
} else {
fmt.Printf("Unknown command %s\n", cmd)
if len(os.Args) < 4 {
usage()
}
os.Exit(1)
}
return
}
+40 -19
View File
@@ -4,9 +4,11 @@ import (
"flag"
"fmt"
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/sysinit"
"github.com/dotcloud/docker/utils"
"io/ioutil"
"log"
"net"
"os"
"os/signal"
"strconv"
@@ -22,7 +24,7 @@ var (
func main() {
if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
// Running in init mode
docker.SysInit()
sysinit.SysInit()
return
}
// FIXME: Switch d and D ? (to be more sshd like)
@@ -35,9 +37,14 @@ func main() {
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
flInterContainerComm := flag.Bool("enable-container-comm", false, "Enable inter-container communication")
flag.Parse()
if *flVersion {
showVersion()
return
@@ -49,10 +56,9 @@ func main() {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
}
bridge := docker.DefaultNetworkBridge
if *bridgeName != "" {
docker.NetworkBridgeIface = *bridgeName
} else {
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
bridge = *bridgeName
}
if *flDebug {
os.Setenv("DEBUG", "1")
@@ -64,7 +70,26 @@ func main() {
flag.Usage()
return
}
if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
var dns []string
if *flDns != "" {
dns = []string{*flDns}
}
ip := net.ParseIP(*flDefaultIp)
config := &docker.DaemonConfig{
Pidfile: *pidfile,
GraphPath: *flGraphPath,
AutoRestart: *flAutoRestart,
EnableCors: *flEnableCors,
Dns: dns,
EnableIptables: *flEnableIptables,
BridgeIface: bridge,
ProtoAddresses: flHosts,
DefaultIp: ip,
InterContainerCommunication: *flInterContainerComm,
}
if err := daemon(config); err != nil {
log.Fatal(err)
os.Exit(-1)
}
@@ -115,30 +140,26 @@ func removePidFile(pidfile string) {
}
}
func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
if err := createPidFile(pidfile); err != nil {
func daemon(config *docker.DaemonConfig) error {
if err := createPidFile(config.Pidfile); err != nil {
log.Fatal(err)
}
defer removePidFile(pidfile)
defer removePidFile(config.Pidfile)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
go func() {
sig := <-c
log.Printf("Received signal '%v', exiting\n", sig)
removePidFile(pidfile)
removePidFile(config.Pidfile)
os.Exit(0)
}()
var dns []string
if flDns != "" {
dns = []string{flDns}
}
server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
server, err := docker.NewServer(config)
if err != nil {
return err
}
chErrors := make(chan error, len(protoAddrs))
for _, protoAddr := range protoAddrs {
chErrors := make(chan error, len(config.ProtoAddresses))
for _, protoAddr := range config.ProtoAddresses {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if protoAddrParts[0] == "unix" {
syscall.Unlink(protoAddrParts[1])
@@ -154,7 +175,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
}()
}
for i := 0; i < len(protoAddrs); i += 1 {
for i := 0; i < len(config.ProtoAddresses); i += 1 {
err := <-chErrors
if err != nil {
return err
+16
View File
@@ -0,0 +1,16 @@
package main
import (
"github.com/dotcloud/docker/sysinit"
)
var (
GITCOMMIT string
VERSION string
)
func main() {
// Running in init mode
sysinit.SysInit()
return
}
+90 -7
View File
@@ -93,8 +93,8 @@ Examples:
.. _cli_build_examples:
Examples
~~~~~~~~
Examples:
~~~~~~~~~
.. code-block:: bash
@@ -400,6 +400,33 @@ Insert file from github
Kill a running container
.. _cli_link:
``link``
--------
::
Usage: docker link CURRENT_NAME NEW_NAME
Link a container to a new name.
Examples:
~~~~~~~~~
.. code-block:: bash
$ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis
$ docker ls
NAME ID IMAGE
/redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc``
with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``.
.. _cli_login:
``login``
@@ -427,7 +454,6 @@ Insert file from github
``logs``
--------
::
Usage: docker logs [OPTIONS] CONTAINER
@@ -507,6 +533,29 @@ Insert file from github
Usage: docker rm [OPTIONS] CONTAINER
Remove one or more containers
-link="": Remove the link instead of the actual container
Examples:
~~~~~~~~~
.. code-block:: bash
$ docker rm /redis
/redis
This will remove the container referenced under the link ``/redis``.
.. code-block:: bash
$ docker rm -link /webapp/redis
/webapp/redis
This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all
network communication.
.. _cli_rmi:
@@ -530,7 +579,7 @@ Insert file from github
Run a command in a new container
-a=map[]: Attach to stdin, stdout or stderr.
-a=map[]: Attach to stdin, stdout or stderr
-c=0: CPU shares (relative weight)
-cidfile="": Write the container ID to the file
-d=false: Detached mode: Run container in the background, print new container id
@@ -546,13 +595,15 @@ Insert file from github
-u="": Username or UID
-dns=[]: Set custom dns servers for the container
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume.
-volumes-from="": Mount all volumes from the given container.
-entrypoint="": Overwrite the default entrypoint set by the image.
-volumes-from="": Mount all volumes from the given container
-entrypoint="": Overwrite the default entrypoint set by the image
-w="": Working directory inside the container
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
-expose=[]: Expose a port from the container without publishing it to your host
-link="": Add link to another container (containerid:alias)
Examples
~~~~~~~~
--------
.. code-block:: bash
@@ -600,6 +651,38 @@ working directory, by changing into the directory to the value
returned by ``pwd``. So this combination executes the command
using the container, but inside the current working directory.
.. code-block:: bash
docker run -p 127.0.0.0::80 ubuntu bash
This the ``-p`` flag now allows you to bind a port to a specific
interface of the host machine. In this example port ``80`` of the
container will have a dynamically allocated port bound to 127.0.0.1
of the host.
.. code-block:: bash
docker run -p 127.0.0.1:80:80 ubuntu bash
This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your
host machine.
.. code-block:: bash
docker run -expose 80 ubuntu bash
This will expose port ``80`` of the container for use within a link
without publishing the port to the host system's interfaces.
.. code-block:: bash
docker run -link /redis:redis ubuntu bash
The ``-link`` flag will link the container named ``/redis`` into the
newly created container with the alias ``redis``. The new container
can access the network and environment of the redis container via
environment variables.
.. _cli_search:
``search``
+2 -1
View File
@@ -1,6 +1,6 @@
:title: Docker Examples
:description: Examples on how to use Docker
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql, link
.. _example_list:
@@ -24,3 +24,4 @@ to more substantial services like you might find in production.
postgresql_service
mongodb
running_riak_service
linking_into_redis
@@ -0,0 +1,132 @@
:title: Linking to an Redis container
:description: Running redis linked into your web app
:keywords: docker, example, networking, redis, link
.. _linking_redis:
Linking Redis
=============
.. include:: example_header.inc
Building a redis container to link as a child of our web application.
Building the redis container
----------------------------
We will use a pre-build version of redis from the index under
the name ``crosbymichael/redis``. If you are interested in the
Dockerfile that was used to build this container here it is.
.. code-block:: bash
# Build redis from source
# Make sure you have the redis source code checked out in
# the same directory as this Dockerfile
FROM ubuntu
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl
ADD . /redis
RUN (cd /redis && make)
RUN (cd /redis && make test)
RUN mkdir -p /redis-data
VOLUME ["/redis-data"]
EXPOSE 6379
ENTRYPOINT ["/redis/src/redis-server"]
CMD ["--dir", "/redis-data"]
We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports
to connect to our redis container on. If you do not expose any ports for the
image then docker will not be able to establish the link between containers.
Run the redis container
-----------------------
.. code-block:: bash
docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass docker
This will run our redis container using the default port of 6379 and using
as password to secure our service. Next we will link the redis container to
a new name using ``docker link``.
Linking an existing container
-----------------------------
Docker will automatically create an initial link with the container's id but
because the is long and not very user friendly we can link the container with
a new name.
.. code-block:: bash
docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis
Now we can reference our running redis service using the friendly name ``/redis``.
We can issue all the commands that you would expect; start, stop, attach, using the new name.
Linking redis as a child
------------------------
Next we can start a new web application that has a dependency on redis and apply a link
to connect both containers. If you noticed when running our redis service we did not use
the ``-p`` option to publish the redis port to the host system. Redis exposed port 6379
but we did not publish the port. This allows docker to prevent all network traffic to
the redis container except when explicitly specified within a link. This is a big win
for security.
Now lets start our web application with a link into redis.
.. code-block:: bash
docker run -t -i -link /redis:db ubuntu bash
root@4c01db0b339c:/# env
HOSTNAME=4c01db0b339c
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
TERM=xterm
DB_PORT=tcp://172.17.0.8:6379
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
DB_ENV_PASSWORD=dockerpass
SHLVL=1
HOME=/
container=lxc
_=/usr/bin/env
root@4c01db0b339c:/#
When we inspect the environment of the linked container we can see a few extra environment
variables have been added. When you specified ``-link /redis:db`` you are telling docker
to link the container named ``/redis`` into this new container with the alias ``db``.
Environment variables are prefixed with the alias so that the parent container can access
network and environment information from the child.
.. code-block:: bash
# The name of the child container
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
# The default protocol, ip, and port of the service running in the container
DB_PORT=tcp://172.17.0.8:6379
# A specific protocol, ip, and port of various services
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
# Get environment variables of the container
DB_ENV_PASSWORD=dockerpass
Accessing the network information along with the environment of the child container allows
us to easily connect to the redis service on the specific ip and port and use the password
specified in the environment.
+1
View File
@@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
+462
View File
@@ -0,0 +1,462 @@
package gograph
import (
_ "code.google.com/p/gosqlite/sqlite3"
"database/sql"
"fmt"
"os"
"path"
)
const (
createEntityTable = `
CREATE TABLE IF NOT EXISTS entity (
id text NOT NULL PRIMARY KEY
);`
createEdgeTable = `
CREATE TABLE IF NOT EXISTS edge (
"entity_id" text NOT NULL,
"parent_id" text NULL,
"name" text NOT NULL,
CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
);
`
createEdgeIndices = `
CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name);
`
)
// Entity with a unique id
type Entity struct {
id string
}
// An Edge connects two entities together
type Edge struct {
EntityID string
Name string
ParentID string
}
type Entities map[string]*Entity
type Edges []*Edge
type WalkFunc func(fullPath string, entity *Entity) error
// Graph database for storing entities and their relationships
type Database struct {
dbPath string
}
// Create a new graph database initialized with a root entity
func NewDatabase(dbPath string) (*Database, error) {
db := &Database{dbPath}
if _, err := os.Stat(dbPath); err == nil {
return db, nil
}
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeIndices); err != nil {
return nil, err
}
rollback := func() {
conn.Exec("ROLLBACK")
}
// Create root entities
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
return db, nil
}
// Set the entity id for a given path
func (db *Database) Set(fullPath, id string) (*Entity, error) {
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
// FIXME: is rollback implicit when closing the connection?
rollback := func() {
conn.Exec("ROLLBACK")
}
// FIXME: use exclusive transactions to avoid race conditions
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
var entityId string
if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
if err == sql.ErrNoRows {
if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
rollback()
return nil, err
}
} else {
rollback()
return nil, err
}
}
e := &Entity{id}
parentPath, name := splitPath(fullPath)
if err := db.setEdge(conn, parentPath, name, e); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
return e, nil
}
func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error {
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
if parent.id == e.id {
return fmt.Errorf("Cannot set self as child")
}
if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
return err
}
return nil
}
// Return the root "/" entity for the database
func (db *Database) RootEntity() *Entity {
return &Entity{
id: "0",
}
}
// Return the entity for a given path
func (db *Database) Get(name string) *Entity {
conn, err := db.openConn()
if err != nil {
return nil
}
e, err := db.get(conn, name)
if err != nil {
return nil
}
return e
}
func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
e := db.RootEntity()
// We always know the root name so return it if
// it is requested
if name == "/" {
return e, nil
}
parts := split(name)
for i := 1; i < len(parts); i++ {
p := parts[i]
next := db.child(conn, e, p)
if next == nil {
return nil, fmt.Errorf("Cannot find child")
}
e = next
}
return e, nil
}
// List all entities by from the name
// The key will be the full path of the entity
func (db *Database) List(name string, depth int) Entities {
out := Entities{}
conn, err := db.openConn()
if err != nil {
return out
}
defer conn.Close()
for c := range db.children(conn, name, depth) {
out[c.FullPath] = c.Entity
}
return out
}
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
for c := range db.children(conn, name, depth) {
if err := walkFunc(c.FullPath, c.Entity); err != nil {
return err
}
}
return nil
}
// Return the refrence count for a specified id
func (db *Database) Refs(id string) int {
conn, err := db.openConn()
if err != nil {
return -1
}
defer conn.Close()
var count int
if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
return 0
}
return count
}
// Return all the id's path references
func (db *Database) RefPaths(id string) Edges {
refs := Edges{}
conn, err := db.openConn()
if err != nil {
return refs
}
defer conn.Close()
rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
if err != nil {
return refs
}
defer rows.Close()
for rows.Next() {
var name string
var parentId string
if err := rows.Scan(&name, &parentId); err != nil {
return refs
}
refs = append(refs, &Edge{
EntityID: id,
Name: name,
ParentID: parentId,
})
}
return refs
}
// Delete the reference to an entity at a given path
func (db *Database) Delete(name string) error {
if name == "/" {
return fmt.Errorf("Cannot delete root entity")
}
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parentPath, n := splitPath(name)
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, n+"%"); err != nil {
return err
}
return nil
}
// Remove the entity with the specified id
// Walk the graph to make sure all references to the entity
// are removed and return the number of references removed
func (db *Database) Purge(id string) (int, error) {
conn, err := db.openConn()
if err != nil {
return -1, err
}
defer conn.Close()
rollback := func() {
conn.Exec("ROLLBACK")
}
if _, err := conn.Exec("BEGIN"); err != nil {
return -1, err
}
// Delete all edges
rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
if err != nil {
rollback()
return -1, err
}
changes, err := rows.RowsAffected()
if err != nil {
return -1, err
}
// Delete entity
if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
rollback()
return -1, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return -1, err
}
return int(changes), nil
}
// Rename an edge for a given path
func (db *Database) Rename(currentName, newName string) error {
parentPath, name := splitPath(currentName)
newParentPath, newEdgeName := splitPath(newName)
if parentPath != newParentPath {
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
}
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name LIKE ?;", newEdgeName, parent.id, name+"%")
if err != nil {
return err
}
i, err := rows.RowsAffected()
if err != nil {
return err
}
if i == 0 {
return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
}
return nil
}
type WalkMeta struct {
Parent *Entity
Entity *Entity
FullPath string
Edge *Edge
}
func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta {
out := make(chan WalkMeta)
e, err := db.get(conn, name)
if err != nil {
close(out)
return out
}
go func() {
rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
if err != nil {
close(out)
}
defer rows.Close()
for rows.Next() {
var entityId, entityName string
if err := rows.Scan(&entityId, &entityName); err != nil {
// Log error
continue
}
child := &Entity{entityId}
edge := &Edge{
ParentID: e.id,
Name: entityName,
EntityID: child.id,
}
meta := WalkMeta{
Parent: e,
Entity: child,
FullPath: path.Join(name, edge.Name),
Edge: edge,
}
out <- meta
if depth == 0 {
continue
}
nDepth := depth
if depth != -1 {
nDepth -= 1
}
sc := db.children(conn, meta.FullPath, nDepth)
for c := range sc {
out <- c
}
}
close(out)
}()
return out
}
// Return the entity based on the parent path and name
func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity {
var id string
if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, name+"%").Scan(&id); err != nil {
return nil
}
return &Entity{id}
}
func (db *Database) openConn() (*sql.DB, error) {
return sql.Open("sqlite3", db.dbPath)
}
// Return the id used to reference this entity
func (e *Entity) ID() string {
return e.id
}
// Return the paths sorted by depth
func (e Entities) Paths() []string {
out := make([]string, len(e))
var i int
for k := range e {
out[i] = k
i++
}
sortByDepth(out)
return out
}
+464
View File
@@ -0,0 +1,464 @@
package gograph
import (
"os"
"path"
"strconv"
"testing"
)
func newTestDb(t *testing.T) *Database {
db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"))
if err != nil {
t.Fatal(err)
}
return db
}
func destroyTestDb(db *Database) {
os.Remove(db.dbPath)
}
func TestNewDatabase(t *testing.T) {
db := newTestDb(t)
if db == nil {
t.Fatal("Database should not be nil")
}
defer destroyTestDb(db)
}
func TestCreateRootEnity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
root := db.RootEntity()
if root == nil {
t.Fatal("Root entity should not be nil")
}
}
func TestGetRootEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
e := db.Get("/")
if e == nil {
t.Fatal("Entity should not be nil")
}
if e.ID() != "0" {
t.Fatalf("Enity id should be 0, got %s", e.ID())
}
}
func TestSetEntityWithDifferentName(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/test", "1")
if _, err := db.Set("/other", "1"); err != nil {
t.Fatal(err)
}
}
func TestSetDuplicateEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
if _, err := db.Set("/foo", "42"); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/foo", "43"); err == nil {
t.Fatalf("Creating an entry with a duplciate path did not cause an error")
}
}
func TestCreateChild(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
child, err := db.Set("/db", "1")
if err != nil {
t.Fatal(err)
}
if child == nil {
t.Fatal("Child should not be nil")
}
if child.ID() != "1" {
t.Fail()
}
}
func TestListAllRootChildren(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
for i := 1; i < 6; i++ {
a := strconv.Itoa(i)
if _, err := db.Set("/"+a, a); err != nil {
t.Fatal(err)
}
}
entries := db.List("/", -1)
if len(entries) != 5 {
t.Fatalf("Expect 5 entries for / got %d", len(entries))
}
}
func TestListAllSubChildren(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
entries := db.List("/webapp", 1)
if len(entries) != 3 {
t.Fatalf("Expect 3 entries for / got %d", len(entries))
}
entries = db.List("/webapp", 0)
if len(entries) != 2 {
t.Fatalf("Expect 2 entries for / got %d", len(entries))
}
}
func TestAddSelfAsChild(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
child, err := db.Set("/test", "1")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/test/other", child.ID()); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestAddChildToNonExistantRoot(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
if _, err := db.Set("/myapp", "1"); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestWalkAll(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/db/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
if err := db.Walk("/", func(p string, e *Entity) error {
t.Logf("Path: %s Entity: %s", p, e.ID())
return nil
}, -1); err != nil {
t.Fatal(err)
}
}
func TestGetEntityByPath(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
entity := db.Get("/webapp/db/logs")
if entity == nil {
t.Fatal("Entity should not be nil")
}
if entity.ID() != "4" {
t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
}
}
func TestEnitiesPaths(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
out := db.List("/", -1)
for _, p := range out.Paths() {
t.Log(p)
}
}
func TestDeleteRootEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
if err := db.Delete("/"); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestDeleteEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
}
child2, err := db.Set("/db", "2")
if err != nil {
t.Fatal(err)
}
child4, err := db.Set("/logs", "4")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
t.Fatal(err)
}
child3, err := db.Set("/sentry", "3")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
t.Fatal(err)
}
child5, err := db.Set("/gograph", "5")
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
t.Fatal(err)
}
if err := db.Delete("/webapp/sentry"); err != nil {
t.Fatal(err)
}
entity := db.Get("/webapp/sentry")
if entity != nil {
t.Fatal("Entity /webapp/sentry should be nil")
}
}
func TestCountRefs(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
if db.Refs("1") != 1 {
t.Fatal("Expect reference count to be 1")
}
db.Set("/db", "2")
db.Set("/webapp/db", "2")
if db.Refs("2") != 2 {
t.Fatal("Expect reference count to be 2")
}
}
func TestPurgeId(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
if db.Refs("1") != 1 {
t.Fatal("Expect reference count to be 1")
}
db.Set("/db", "2")
db.Set("/webapp/db", "2")
count, err := db.Purge("2")
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Fatal("Expected 2 references to be removed")
}
}
func TestRename(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
if db.Refs("1") != 1 {
t.Fatal("Expect reference count to be 1")
}
db.Set("/db", "2")
db.Set("/webapp/db", "2")
if db.Get("/webapp/db") == nil {
t.Fatal("Cannot find entity at path /webapp/db")
}
if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
t.Fatal(err)
}
if db.Get("/webapp/db") != nil {
t.Fatal("Entity should not exist at /webapp/db")
}
if db.Get("/webapp/newdb") == nil {
t.Fatal("Cannot find entity at path /webapp/newdb")
}
}
func TestCreateMultipleNames(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/db", "1")
if _, err := db.Set("/myapp", "1"); err != nil {
t.Fatal(err)
}
db.Walk("/", func(p string, e *Entity) error {
t.Logf("%s\n", p)
return nil
}, -1)
}
func TestRefPaths(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
db.Set("/db", "2")
db.Set("/webapp/db", "2")
refs := db.RefPaths("2")
if len(refs) != 2 {
t.Fatalf("Expected reference count to be 2, got %d", len(refs))
}
}
+27
View File
@@ -0,0 +1,27 @@
package gograph
import "sort"
type pathSorter struct {
paths []string
by func(i, j string) bool
}
func sortByDepth(paths []string) {
s := &pathSorter{paths, func(i, j string) bool {
return pathDepth(i) > pathDepth(j)
}}
sort.Sort(s)
}
func (s *pathSorter) Len() int {
return len(s.paths)
}
func (s *pathSorter) Swap(i, j int) {
s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
}
func (s *pathSorter) Less(i, j int) bool {
return s.by(s.paths[i], s.paths[j])
}
+29
View File
@@ -0,0 +1,29 @@
package gograph
import (
"testing"
)
func TestSort(t *testing.T) {
paths := []string{
"/",
"/myreallylongname",
"/app/db",
}
sortByDepth(paths)
if len(paths) != 3 {
t.Fatalf("Expected 3 parts got %d", len(paths))
}
if paths[0] != "/app/db" {
t.Fatalf("Expected /app/db got %s", paths[0])
}
if paths[1] != "/myreallylongname" {
t.Fatalf("Expected /myreallylongname got %s", paths[1])
}
if paths[2] != "/" {
t.Fatalf("Expected / got %s", paths[2])
}
}
+32
View File
@@ -0,0 +1,32 @@
package gograph
import (
"path"
"strings"
)
// Split p on /
func split(p string) []string {
return strings.Split(p, "/")
}
// Returns the depth or number of / in a given path
func pathDepth(p string) int {
parts := split(p)
if len(parts) == 2 && parts[1] == "" {
return 1
}
return len(parts)
}
func splitPath(p string) (parent, name string) {
if p[0] != '/' {
p = "/" + p
}
parent, name = path.Split(p)
l := len(parent)
if parent[l-1] == '/' {
parent = parent[:l-1]
}
return
}
+5 -2
View File
@@ -121,6 +121,9 @@ func TestRegister(t *testing.T) {
}
func TestMount(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
archive, err := fakeTar()
@@ -144,12 +147,12 @@ func TestMount(t *testing.T) {
if err := os.MkdirAll(rw, 0700); err != nil {
t.Fatal(err)
}
if err := image.Mount(rootfs, rw); err != nil {
if err := image.Mount(runtime, rootfs, rw, "testing"); err != nil {
t.Fatal(err)
}
// FIXME: test for mount contents
defer func() {
if err := Unmount(rootfs); err != nil {
if err := image.Unmount(runtime, rootfs, "testing"); err != nil {
t.Error(err)
}
}()
+11 -14
View File
@@ -32,14 +32,14 @@ the process.
## System build dependencies
To build docker, you will need the following system dependencies
To build docker, you will need the following system dependencies:
* An amd64 machine
* A recent version of git and mercurial
* Go version 1.1.2
* Go version 1.2rc1
* A copy of libdevmapper.a (statically compiled), and associated headers
* A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces)
under the path *src/github.com/dotcloud/docker*. See
under the path *src/github.com/dotcloud/docker*.
## Go dependencies
@@ -55,15 +55,13 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of
please get in touch so we can remediate! Who knows what discrepancies can be caused by even the
slightest deviation. We promise to do our best to make everybody happy.
## Disabling CGO for the net package
## Disabling CGO
Make sure to disable CGO on your system, and then recompile the standard library on the build
machine:
Make sure to disable CGO on your system for the net package using `-tags netgo`,
and then recompile the standard library on the build machine:
```bash
export CGO_ENABLED=0
cd /tmp && echo 'package main' > t.go && go test -a -i -v
go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
```
## Building Docker
@@ -71,7 +69,7 @@ cd /tmp && echo 'package main' > t.go && go test -a -i -v
To build the docker binary, run the following command with the source checkout as the
working directory:
```
```bash
./hack/make.sh binary
```
@@ -80,9 +78,9 @@ This will create a static binary under *./bundles/$VERSION/binary/docker-$VERSIO
You are encouraged to use ./hack/make.sh without modification. If you must absolutely write
your own script (are you really, really sure you need to? make.sh is really not that complicated),
then please take care the respect the following:
then please take care to respect the following:
* In *./hack/make.sh*: $LDFLAGS, $VERSION and $GITCOMMIT
* In *./hack/make.sh*: $LDFLAGS, $BUILDFLAGS, $VERSION and $GITCOMMIT
* In *./hack/make/binary*: the exact build command to run
You may be tempted to tweak these settings. In particular, being a rigorous maintainer, you may want
@@ -106,7 +104,6 @@ dependencies to be installed (see below).
The test suite will also download a small test container, so you will need internet connectivity.
## Runtime dependencies
To run properly, docker needs the following software to be installed at runtime:
+2 -1
View File
@@ -44,7 +44,8 @@ if [ -n "$(git status --porcelain)" ]; then
fi
# Use these flags when compiling the tests and final binary
LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w"
LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-all"'
BUILDFLAGS='-tags netgo'
bundle() {
+1 -1
View File
@@ -2,6 +2,6 @@
DEST=$1
if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then
if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker; then
echo "Created binary: $DEST/docker-$VERSION"
fi
+25 -1
View File
@@ -1,3 +1,5 @@
#!/bin/sh
DEST=$1
set -e
@@ -14,7 +16,7 @@ bundle_test() {
for test_dir in $(find_test_dirs); do (
set -x
cd $test_dir
go test -v -ldflags "$LDFLAGS" $TESTFLAGS
go test -v -ldflags "$LDFLAGS" $BUILDFLAGS $TESTFLAGS
) done
} 2>&1 | tee $DEST/test.log
}
@@ -29,4 +31,26 @@ find_test_dirs() {
sort -u
}
check_test_garbage() {
nGarbage=`find /tmp/ -name 'docker-*' | wc -l`
if [ $nGarbage -gt 0 ]; then
(
cat <<EOF
Error: there are $nGarbage docker-related files in /tmp. Please clean them up and try again.
--------------------------------------------------------------------------------------------
EOF
find /tmp -name 'docker-*'
cat <<EOF
--------------------------------------------------------------------------------------------
If the error persists, your tests might leak temporary files. This is considered a test fail.
EOF
exit 1
) 2>&1
fi
}
check_test_garbage
bundle_test
check_test_garbage
+365 -37
View File
@@ -5,16 +5,16 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/devmapper"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
)
@@ -61,6 +61,7 @@ func LoadImage(root string) (*Image, error) {
}
// Check that the filesystem layer exists
// FIXME: once an image is added into device mapper, the layer is no longer needed
if stat, err := os.Stat(layerPath(root)); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID)
@@ -136,29 +137,8 @@ func jsonPath(root string) string {
return path.Join(root, "json")
}
func MountAUFS(ro []string, rw string, target string) error {
// FIXME: Now mount the layers
rwBranch := fmt.Sprintf("%v=rw", rw)
roBranches := ""
for _, layer := range ro {
roBranches += fmt.Sprintf("%v=ro+wh:", layer)
}
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
branches += ",xino=/dev/shm/aufs.xino"
//if error, try to load aufs kernel module
if err := mount("none", target, "aufs", 0, branches); err != nil {
log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
if err := exec.Command("modprobe", "aufs").Run(); err != nil {
return fmt.Errorf("Unable to load the AUFS module")
}
log.Printf("...module loaded.")
if err := mount("none", target, "aufs", 0, branches); err != nil {
return fmt.Errorf("Unable to mount using aufs")
}
}
return nil
func mountPath(root string) string {
return path.Join(root, "mount")
}
// TarLayer returns a tar archive of the image's filesystem layer.
@@ -170,35 +150,383 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) {
return Tar(layerPath, compression)
}
func (image *Image) Mount(root, rw string) error {
if mounted, err := Mounted(root); err != nil {
return err
} else if mounted {
return fmt.Errorf("%s is already mounted", root)
}
layers, err := image.layers()
type TimeUpdate struct {
path string
time []syscall.Timeval
mode uint32
}
func (image *Image) applyLayer(layer, target string) error {
var updateTimes []TimeUpdate
oldmask := syscall.Umask(0)
defer syscall.Umask(oldmask)
err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip root
if srcPath == layer {
return nil
}
var srcStat syscall.Stat_t
err = syscall.Lstat(srcPath, &srcStat)
if err != nil {
return err
}
relPath, err := filepath.Rel(layer, srcPath)
if err != nil {
return err
}
targetPath := filepath.Join(target, relPath)
// Skip AUFS metadata
if matched, err := filepath.Match(".wh..wh.*", relPath); err != nil || matched {
if err != nil || !f.IsDir() {
return err
}
return filepath.SkipDir
}
// Find out what kind of modification happened
file := filepath.Base(srcPath)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
deletePath := filepath.Join(filepath.Dir(targetPath), originalFile)
err = os.RemoveAll(deletePath)
if err != nil {
return err
}
} else {
var targetStat = &syscall.Stat_t{}
err := syscall.Lstat(targetPath, targetStat)
if err != nil {
if !os.IsNotExist(err) {
return err
}
targetStat = nil
}
if targetStat != nil && !(targetStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR && srcStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR) {
// Unless both src and dest are directories we remove the target and recreate it
// This is a bit wasteful in the case of only a mode change, but that is unlikely
// to matter much
err = os.RemoveAll(targetPath)
if err != nil {
return err
}
targetStat = nil
}
if f.IsDir() {
// Source is a directory
if targetStat == nil {
err = syscall.Mkdir(targetPath, srcStat.Mode&07777)
if err != nil {
return err
}
}
} else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK {
// Source is symlink
link, err := os.Readlink(srcPath)
if err != nil {
return err
}
err = os.Symlink(link, targetPath)
if err != nil {
return err
}
} else if srcStat.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
srcStat.Mode&syscall.S_IFCHR == syscall.S_IFCHR ||
srcStat.Mode&syscall.S_IFIFO == syscall.S_IFIFO ||
srcStat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK {
// Source is special file
err = syscall.Mknod(targetPath, srcStat.Mode, int(srcStat.Rdev))
if err != nil {
return err
}
} else if srcStat.Mode&syscall.S_IFREG == syscall.S_IFREG {
// Source is regular file
fd, err := syscall.Open(targetPath, syscall.O_CREAT|syscall.O_WRONLY, srcStat.Mode&07777)
if err != nil {
return err
}
dstFile := os.NewFile(uintptr(fd), targetPath)
srcFile, err := os.Open(srcPath)
if err != nil {
_ = dstFile.Close()
return err
}
err = CopyFile(dstFile, srcFile)
_ = dstFile.Close()
_ = srcFile.Close()
if err != nil {
return err
}
} else {
return fmt.Errorf("Unknown type for file %s", srcPath)
}
err = syscall.Lchown(targetPath, int(srcStat.Uid), int(srcStat.Gid))
if err != nil {
return err
}
if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK {
err = syscall.Chmod(targetPath, srcStat.Mode&07777)
if err != nil {
return err
}
}
ts := []syscall.Timeval{
syscall.NsecToTimeval(srcStat.Atim.Nano()),
syscall.NsecToTimeval(srcStat.Mtim.Nano()),
}
u := TimeUpdate{
path: targetPath,
time: ts,
mode: srcStat.Mode,
}
// Delay time updates until all other changes done, or it is
// overwritten for directories (by child changes)
updateTimes = append(updateTimes, u)
}
return nil
})
if err != nil {
return err
}
// We do this in reverse order so that children are updated before parents
for i := len(updateTimes) - 1; i >= 0; i-- {
update := updateTimes[i]
O_PATH := 010000000 // Not in syscall yet
var err error
if update.mode&syscall.S_IFLNK == syscall.S_IFLNK {
// Update time on the symlink via O_PATH + futimes(), if supported by the kernel
fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600)
if err == syscall.EISDIR || err == syscall.ELOOP {
// O_PATH not supported by kernel, nothing to do, ignore
} else if err != nil {
return err
} else {
syscall.Futimes(fd, update.time)
syscall.Close(fd)
}
} else {
err = syscall.Utimes(update.path, update.time)
if err != nil {
return err
}
}
}
return nil
}
func (image *Image) ensureImageDevice(devices *devmapper.DeviceSet) error {
if devices.HasInitializedDevice(image.ID) {
return nil
}
if image.Parent != "" && !devices.HasInitializedDevice(image.Parent) {
parentImg, err := image.GetParent()
if err != nil {
return fmt.Errorf("Error while getting parent image: %v", err)
}
err = parentImg.ensureImageDevice(devices)
if err != nil {
return err
}
}
root, err := image.root()
if err != nil {
return err
}
mountDir := mountPath(root)
if err := os.Mkdir(mountDir, 0600); err != nil && !os.IsExist(err) {
return err
}
mounted, err := Mounted(mountDir)
if err == nil && mounted {
utils.Debugf("Image %s is unexpectedly mounted, unmounting...", image.ID)
err = syscall.Unmount(mountDir, 0)
if err != nil {
return err
}
}
if devices.HasDevice(image.ID) {
utils.Debugf("Found non-initialized demove-mapper device for image %s, removing", image.ID)
err = devices.RemoveDevice(image.ID)
if err != nil {
return err
}
}
utils.Debugf("Creating device-mapper device for image id %s", image.ID)
if err := devices.AddDevice(image.ID, image.Parent); err != nil {
utils.Debugf("Error add device: %s", err)
return err
}
if err := devices.MountDevice(image.ID, mountDir, false); err != nil {
utils.Debugf("Error mounting device: %s", err)
devices.RemoveDevice(image.ID)
return err
}
if err = image.applyLayer(layerPath(root), mountDir); err != nil {
utils.Debugf("Error applying layer: %s", err)
devices.UnmountDevice(image.ID, mountDir, true)
devices.RemoveDevice(image.ID)
return err
}
// The docker init layer is conceptually above all other layers, so we apply
// it for every image. This is safe because the layer directory is the
// definition of the image, and the device-mapper device is just a cache
// of it instantiated. Diffs/commit compare the container device with the
// image device, which will then *not* pick up the init layer changes as
// part of the container changes
dockerinitLayer, err := image.getDockerInitLayer()
if err != nil {
devices.UnmountDevice(image.ID, mountDir, true)
devices.RemoveDevice(image.ID)
return err
}
if err := image.applyLayer(dockerinitLayer, mountDir); err != nil {
devices.UnmountDevice(image.ID, mountDir, true)
devices.RemoveDevice(image.ID)
return err
}
if err := devices.UnmountDevice(image.ID, mountDir, true); err != nil {
devices.RemoveDevice(image.ID)
return err
}
devices.SetInitialized(image.ID)
return nil
}
func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) {
return Mounted(root)
}
func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error {
if mounted, _ := image.Mounted(runtime, root, rw); mounted {
return fmt.Errorf("%s is already mounted", root)
}
// Create the target directories if they don't exist
if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
return err
}
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
devices, err := runtime.GetDeviceSet()
if err != nil {
return err
}
if err := MountAUFS(layers, rw, root); err != nil {
if err := image.ensureImageDevice(devices); err != nil {
return err
}
if !devices.HasDevice(id) {
utils.Debugf("Creating device %s for container based on image %s", id, image.ID)
err = devices.AddDevice(id, image.ID)
if err != nil {
return err
}
}
utils.Debugf("Mounting container %s at %s for container", id, root)
if err := devices.MountDevice(id, root, false); err != nil {
return err
}
return nil
}
func (image *Image) Changes(rw string) ([]Change, error) {
layers, err := image.layers()
func (image *Image) Unmount(runtime *Runtime, root string, id string) error {
// Try to deactivate the device as generally there is no use for it anymore
devices, err := runtime.GetDeviceSet()
if err != nil {
return err
}
if err = devices.UnmountDevice(id, root, true); err != nil {
return err
}
return nil
}
func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) {
devices, err := runtime.GetDeviceSet()
if err != nil {
return nil, err
}
return Changes(layers, rw)
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
return nil, err
}
wasActivated := devices.HasActivatedDevice(image.ID)
// We re-use rw for the temporary mount of the base image as its
// not used by device-mapper otherwise
err = devices.MountDevice(image.ID, rw, true)
if err != nil {
return nil, err
}
changes, err := ChangesDirs(root, rw)
devices.UnmountDevice(image.ID, rw, !wasActivated)
if err != nil {
return nil, err
}
return changes, nil
}
func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) {
changes, err := image.Changes(runtime, root, rw, id)
if err != nil {
return nil, err
}
files := make([]string, 0)
deletions := make([]string, 0)
for _, change := range changes {
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
files = append(files, change.Path)
}
if change.Kind == ChangeDelete {
base := filepath.Base(change.Path)
dir := filepath.Dir(change.Path)
deletions = append(deletions, filepath.Join(dir, ".wh."+base))
}
}
return TarFilter(root, Uncompressed, files, false, deletions)
}
func (image *Image) ShortID() string {
+1
View File
@@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
+105
View File
@@ -0,0 +1,105 @@
package iptables
import (
"errors"
"fmt"
"net"
"os/exec"
"strconv"
"strings"
)
type Action string
const (
Add Action = "-A"
Delete Action = "-D"
)
var (
ErrIptablesNotFound = errors.New("Iptables not found")
nat = []string{"-t", "nat"}
)
type Chain struct {
Name string
Bridge string
}
func NewChain(name, bridge string) (*Chain, error) {
if err := Raw("-t", "nat", "-N", name); err != nil {
return nil, err
}
chain := &Chain{
Name: name,
Bridge: bridge,
}
if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil {
return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
}
if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil {
return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
return chain, nil
}
func RemoveExistingChain(name string) error {
chain := &Chain{
Name: name,
}
return chain.Remove()
}
func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error {
return Raw("-t", "nat", fmt.Sprint(action), c.Name,
"-p", proto,
"-d", ip.String(),
"--dport", strconv.Itoa(port),
"!", "-i", c.Bridge,
"-j", "DNAT",
"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
}
func (c *Chain) Prerouting(action Action, args ...string) error {
a := append(nat, fmt.Sprint(action), "PREROUTING")
if len(args) > 0 {
a = append(a, args...)
}
return Raw(append(a, "-j", c.Name)...)
}
func (c *Chain) Output(action Action, args ...string) error {
a := append(nat, fmt.Sprint(action), "OUTPUT")
if len(args) > 0 {
a = append(a, args...)
}
return Raw(append(a, "-j", c.Name)...)
}
func (c *Chain) Remove() error {
// Ignore errors - This could mean the chains were never set up
c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
c.Prerouting(Delete)
c.Output(Delete)
Raw("-t", "nat", "-F", c.Name)
Raw("-t", "nat", "-X", c.Name)
return nil
}
func Raw(args ...string) error {
path, err := exec.LookPath("iptables")
if err != nil {
return ErrIptablesNotFound
}
if err := exec.Command(path, args...).Run(); err != nil {
return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
}
return nil
}
+18
View File
@@ -0,0 +1,18 @@
package iptables
import (
"os"
"testing"
)
func TestIptables(t *testing.T) {
if err := Raw("-L"); err != nil {
t.Fatal(err)
}
path := os.Getenv("PATH")
os.Setenv("PATH", "")
defer os.Setenv("PATH", path)
if err := Raw("-L"); err == nil {
t.Fatal("Not finding iptables in the PATH should cause an error")
}
}
+141
View File
@@ -0,0 +1,141 @@
package docker
import (
"fmt"
"github.com/dotcloud/docker/iptables"
"path"
"strings"
)
type Link struct {
ParentIP string
ChildIP string
Name string
BridgeInterface string
ChildEnvironment []string
Ports []Port
IsEnabled bool
}
func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, error) {
if parent.ID == child.ID {
return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
}
if !child.State.Running {
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.ID, name)
}
ports := make([]Port, len(child.Config.ExposedPorts))
var i int
for p := range child.Config.ExposedPorts {
ports[i] = p
i++
}
l := &Link{
BridgeInterface: bridgeInterface,
Name: name,
ChildIP: child.NetworkSettings.IPAddress,
ParentIP: parent.NetworkSettings.IPAddress,
ChildEnvironment: child.Config.Env,
Ports: ports,
}
return l, nil
}
func (l *Link) Alias() string {
_, alias := path.Split(l.Name)
return alias
}
func (l *Link) ToEnv() []string {
env := []string{}
alias := strings.ToUpper(l.Alias())
if p := l.getDefaultPort(); p != nil {
env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port()))
}
// Load exposed ports into the environment
for _, p := range l.Ports {
env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
}
// Load the linked container's name into the environment
env = append(env, fmt.Sprintf("%s_NAME=%s", alias, l.Name))
if l.ChildEnvironment != nil {
for _, v := range l.ChildEnvironment {
parts := strings.Split(v, "=")
if len(parts) != 2 {
continue
}
// Ignore a few variables that are added during docker build
if parts[0] == "HOME" || parts[0] == "PATH" {
continue
}
env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1]))
}
}
return env
}
// Default port rules
func (l *Link) getDefaultPort() *Port {
var p Port
i := len(l.Ports)
if i == 0 {
return nil
} else if i > 1 {
sortPorts(l.Ports, func(ip, jp Port) bool {
// If the two ports have the same number, tcp takes priority
// Sort in desc order
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp")
})
}
p = l.Ports[0]
return &p
}
func (l *Link) Enable() error {
if err := l.toggle("-I", false); err != nil {
return err
}
l.IsEnabled = true
return nil
}
func (l *Link) Disable() {
// We do not care about errors here because the link may not
// exist in iptables
l.toggle("-D", true)
l.IsEnabled = false
}
func (l *Link) toggle(action string, ignoreErrors bool) error {
for _, p := range l.Ports {
if err := iptables.Raw(action, "FORWARD",
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
"-p", p.Proto(),
"-s", l.ParentIP,
"--dport", p.Port(),
"-d", l.ChildIP,
"-j", "ACCEPT"); !ignoreErrors && err != nil {
return err
}
if err := iptables.Raw(action, "FORWARD",
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
"-p", p.Proto(),
"-s", l.ChildIP,
"--sport", p.Port(),
"-d", l.ParentIP,
"-j", "ACCEPT"); !ignoreErrors && err != nil {
return err
}
}
return nil
}
+104
View File
@@ -0,0 +1,104 @@
package docker
import (
"strings"
"testing"
)
func newMockLinkContainer(id string, ip string) *Container {
return &Container{
Config: &Config{},
ID: id,
NetworkSettings: &NetworkSettings{
IPAddress: ip,
},
}
}
func TestLinkNew(t *testing.T) {
toID := GenerateID()
fromID := GenerateID()
from := newMockLinkContainer(fromID, "172.0.17.2")
from.Config.Env = []string{}
from.State = State{Running: true}
ports := make(map[Port]struct{})
ports[Port("6379/tcp")] = struct{}{}
from.Config.ExposedPorts = ports
to := newMockLinkContainer(toID, "172.0.17.3")
link, err := NewLink(to, from, "/db/docker", "172.0.17.1")
if err != nil {
t.Fatal(err)
}
if link == nil {
t.FailNow()
}
if link.Name != "/db/docker" {
t.Fail()
}
if link.Alias() != "docker" {
t.Fail()
}
if link.ParentIP != "172.0.17.3" {
t.Fail()
}
if link.ChildIP != "172.0.17.2" {
t.Fail()
}
if link.BridgeInterface != "172.0.17.1" {
t.Fail()
}
for _, p := range link.Ports {
if p != Port("6379/tcp") {
t.Fail()
}
}
}
func TestLinkEnv(t *testing.T) {
toID := GenerateID()
fromID := GenerateID()
from := newMockLinkContainer(fromID, "172.0.17.2")
from.Config.Env = []string{"PASSWORD=gordon"}
from.State = State{Running: true}
ports := make(map[Port]struct{})
ports[Port("6379/tcp")] = struct{}{}
from.Config.ExposedPorts = ports
to := newMockLinkContainer(toID, "172.0.17.3")
link, err := NewLink(to, from, "/db/docker", "172.0.17.1")
if err != nil {
t.Fatal(err)
}
rawEnv := link.ToEnv()
env := make(map[string]string, len(rawEnv))
for _, e := range rawEnv {
parts := strings.Split(e, "=")
if len(parts) != 2 {
t.FailNow()
}
env[parts[0]] = parts[1]
}
if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" {
t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT"])
}
if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" {
t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP"])
}
if env["DOCKER_NAME"] != "/db/docker" {
t.Fatalf("Expected /db/docker, got %s", env["DOCKER_NAME"])
}
if env["DOCKER_ENV_PASSWORD"] != "gordon" {
t.Fatalf("Expected gordon, got %s", env["DOCKER_ENV_PASSWORD"])
}
}
+1 -1
View File
@@ -92,7 +92,7 @@ lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,no
#lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
# Inject docker-init
# Inject dockerinit
lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/.dockerinit none bind,ro 0 0
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
-29
View File
@@ -1,40 +1,11 @@
package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
)
func Unmount(target string) error {
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
utils.Errorf("[warning]: couldn't run auplink before unmount: %s", err)
}
if err := syscall.Unmount(target, 0); err != nil {
return err
}
// Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint
// for some time. We'll just keep retrying until it succeeds.
for retries := 0; retries < 1000; retries++ {
err := os.Remove(target)
if err == nil {
// rm mntpoint succeeded
return nil
}
if os.IsNotExist(err) {
// mntpoint doesn't exist anymore. Success.
return nil
}
// fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err)
time.Sleep(10 * time.Millisecond)
}
return fmt.Errorf("Umount: Failed to umount %v", target)
}
func Mounted(mountpoint string) (bool, error) {
mntpoint, err := os.Stat(mountpoint)
if err != nil {
+548
View File
@@ -0,0 +1,548 @@
package netlink
import (
"encoding/binary"
"fmt"
"net"
"syscall"
"unsafe"
)
var nextSeqNr int
func nativeEndian() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}
func getSeq() int {
nextSeqNr = nextSeqNr + 1
return nextSeqNr
}
func getIpFamily(ip net.IP) int {
if len(ip) <= net.IPv4len {
return syscall.AF_INET
}
if ip.To4() != nil {
return syscall.AF_INET
}
return syscall.AF_INET6
}
type NetlinkRequestData interface {
ToWireFormat() []byte
}
type IfInfomsg struct {
syscall.IfInfomsg
}
func newIfInfomsg(family int) *IfInfomsg {
msg := &IfInfomsg{}
msg.Family = uint8(family)
msg.Type = uint16(0)
msg.Index = int32(0)
msg.Flags = uint32(0)
msg.Change = uint32(0)
return msg
}
func (msg *IfInfomsg) ToWireFormat() []byte {
native := nativeEndian()
len := syscall.SizeofIfInfomsg
b := make([]byte, len)
b[0] = msg.Family
b[1] = 0
native.PutUint16(b[2:4], msg.Type)
native.PutUint32(b[4:8], uint32(msg.Index))
native.PutUint32(b[8:12], msg.Flags)
native.PutUint32(b[12:16], msg.Change)
return b
}
type IfAddrmsg struct {
syscall.IfAddrmsg
}
func newIfAddrmsg(family int) *IfAddrmsg {
msg := &IfAddrmsg{}
msg.Family = uint8(family)
msg.Prefixlen = uint8(0)
msg.Flags = uint8(0)
msg.Scope = uint8(0)
msg.Index = uint32(0)
return msg
}
func (msg *IfAddrmsg) ToWireFormat() []byte {
native := nativeEndian()
len := syscall.SizeofIfAddrmsg
b := make([]byte, len)
b[0] = msg.Family
b[1] = msg.Prefixlen
b[2] = msg.Flags
b[3] = msg.Scope
native.PutUint32(b[4:8], msg.Index)
return b
}
type RtMsg struct {
syscall.RtMsg
}
func newRtMsg(family int) *RtMsg {
msg := &RtMsg{}
msg.Family = uint8(family)
msg.Table = syscall.RT_TABLE_MAIN
msg.Scope = syscall.RT_SCOPE_UNIVERSE
msg.Protocol = syscall.RTPROT_BOOT
msg.Type = syscall.RTN_UNICAST
return msg
}
func (msg *RtMsg) ToWireFormat() []byte {
native := nativeEndian()
len := syscall.SizeofRtMsg
b := make([]byte, len)
b[0] = msg.Family
b[1] = msg.Dst_len
b[2] = msg.Src_len
b[3] = msg.Tos
b[4] = msg.Table
b[5] = msg.Protocol
b[6] = msg.Scope
b[7] = msg.Type
native.PutUint32(b[8:12], msg.Flags)
return b
}
func rtaAlignOf(attrlen int) int {
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
}
type RtAttr struct {
syscall.RtAttr
Data []byte
}
func newRtAttr(attrType int, data []byte) *RtAttr {
attr := &RtAttr{}
attr.Type = uint16(attrType)
attr.Data = data
return attr
}
func (attr *RtAttr) ToWireFormat() []byte {
native := nativeEndian()
len := syscall.SizeofRtAttr + len(attr.Data)
b := make([]byte, rtaAlignOf(len))
native.PutUint16(b[0:2], uint16(len))
native.PutUint16(b[2:4], attr.Type)
for i, d := range attr.Data {
b[4+i] = d
}
return b
}
type NetlinkRequest struct {
syscall.NlMsghdr
Data []NetlinkRequestData
}
func (rr *NetlinkRequest) ToWireFormat() []byte {
native := nativeEndian()
length := rr.Len
dataBytes := make([][]byte, len(rr.Data))
for i, data := range rr.Data {
dataBytes[i] = data.ToWireFormat()
length = length + uint32(len(dataBytes[i]))
}
b := make([]byte, length)
native.PutUint32(b[0:4], length)
native.PutUint16(b[4:6], rr.Type)
native.PutUint16(b[6:8], rr.Flags)
native.PutUint32(b[8:12], rr.Seq)
native.PutUint32(b[12:16], rr.Pid)
i := 16
for _, data := range dataBytes {
for _, dataByte := range data {
b[i] = dataByte
i = i + 1
}
}
return b
}
func (rr *NetlinkRequest) AddData(data NetlinkRequestData) {
rr.Data = append(rr.Data, data)
}
func newNetlinkRequest(proto, flags int) *NetlinkRequest {
rr := &NetlinkRequest{}
rr.Len = uint32(syscall.NLMSG_HDRLEN)
rr.Type = uint16(proto)
rr.Flags = syscall.NLM_F_REQUEST | uint16(flags)
rr.Seq = uint32(getSeq())
return rr
}
type NetlinkSocket struct {
fd int
lsa syscall.SockaddrNetlink
}
func getNetlinkSocket() (*NetlinkSocket, error) {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE)
if err != nil {
return nil, err
}
s := &NetlinkSocket{
fd: fd,
}
s.lsa.Family = syscall.AF_NETLINK
if err := syscall.Bind(fd, &s.lsa); err != nil {
syscall.Close(fd)
return nil, err
}
return s, nil
}
func (s *NetlinkSocket) Close() {
syscall.Close(s.fd)
}
func (s *NetlinkSocket) Send(request *NetlinkRequest) error {
if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil {
return err
}
return nil
}
func (s *NetlinkSocket) Recieve() ([]syscall.NetlinkMessage, error) {
rb := make([]byte, syscall.Getpagesize())
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
if err != nil {
return nil, err
}
if nr < syscall.NLMSG_HDRLEN {
return nil, fmt.Errorf("Got short response from netlink")
}
rb = rb[:nr]
return syscall.ParseNetlinkMessage(rb)
}
func (s *NetlinkSocket) GetPid() (uint32, error) {
lsa, err := syscall.Getsockname(s.fd)
if err != nil {
return 0, err
}
switch v := lsa.(type) {
case *syscall.SockaddrNetlink:
return v.Pid, nil
}
return 0, fmt.Errorf("Wrong socket type")
}
func (s *NetlinkSocket) HandleAck(seq uint32) error {
native := nativeEndian()
pid, err := s.GetPid()
if err != nil {
return err
}
done:
for {
msgs, err := s.Recieve()
if err != nil {
return err
}
for _, m := range msgs {
if m.Header.Seq != seq {
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq)
}
if m.Header.Pid != pid {
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
error := int32(native.Uint32(m.Data[0:4]))
if error == 0 {
break done
}
return syscall.Errno(-error)
}
}
}
return nil
}
// Add a new default gateway. Identical to:
// ip route add default via $ip
func AddDefaultGw(ip net.IP) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
family := getIpFamily(ip)
wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newRtMsg(family)
wb.AddData(msg)
var ipData []byte
if family == syscall.AF_INET {
ipData = ip.To4()
} else {
ipData = ip.To16()
}
gateway := newRtAttr(syscall.RTA_GATEWAY, ipData)
wb.AddData(gateway)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// Bring up a particular network interface
func NetworkLinkUp(iface *net.Interface) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Change = syscall.IFF_UP
msg.Flags = syscall.IFF_UP
msg.Index = int32(iface.Index)
wb.AddData(msg)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// Add an Ip address to an interface. This is identical to:
// ip addr add $ip/$ipNet dev $iface
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
family := getIpFamily(ip)
wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newIfAddrmsg(family)
msg.Index = uint32(iface.Index)
prefixLen, _ := ipNet.Mask.Size()
msg.Prefixlen = uint8(prefixLen)
wb.AddData(msg)
var ipData []byte
if family == syscall.AF_INET {
ipData = ip.To4()
} else {
ipData = ip.To16()
}
localData := newRtAttr(syscall.IFA_LOCAL, ipData)
wb.AddData(localData)
addrData := newRtAttr(syscall.IFA_ADDRESS, ipData)
wb.AddData(addrData)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
func zeroTerminated(s string) []byte {
bytes := make([]byte, len(s)+1)
for i := 0; i < len(s); i++ {
bytes[i] = s[i]
}
bytes[len(s)] = 0
return bytes
}
func nonZeroTerminated(s string) []byte {
bytes := make([]byte, len(s))
for i := 0; i < len(s); i++ {
bytes[i] = s[i]
}
return bytes
}
// Add a new network link of a specified type. This is identical to
// running: ip add link $name type $linkType
func NetworkLinkAdd(name string, linkType string) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
wb.AddData(msg)
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name))
wb.AddData(nameData)
IFLA_INFO_KIND := 1
kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType))
infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat())
wb.AddData(infoData)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// Returns an array of IPNet for all the currently routed subnets on ipv4
// This is similar to the first column of "ip route" output
func NetworkGetRoutes() ([]*net.IPNet, error) {
native := nativeEndian()
s, err := getNetlinkSocket()
if err != nil {
return nil, err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP)
msg := newIfInfomsg(syscall.AF_UNSPEC)
wb.AddData(msg)
if err := s.Send(wb); err != nil {
return nil, err
}
pid, err := s.GetPid()
if err != nil {
return nil, err
}
res := make([]*net.IPNet, 0)
done:
for {
msgs, err := s.Recieve()
if err != nil {
return nil, err
}
for _, m := range msgs {
if m.Header.Seq != wb.Seq {
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq)
}
if m.Header.Pid != pid {
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
error := int32(native.Uint32(m.Data[0:4]))
if error == 0 {
break done
}
return nil, syscall.Errno(-error)
}
if m.Header.Type != syscall.RTM_NEWROUTE {
continue
}
var iface *net.Interface = nil
var ipNet *net.IPNet = nil
msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0]))
if msg.Flags&syscall.RTM_F_CLONED != 0 {
// Ignore cloned routes
continue
}
if msg.Table != syscall.RT_TABLE_MAIN {
// Ignore non-main tables
continue
}
if msg.Family != syscall.AF_INET {
// Ignore non-ipv4 routes
continue
}
if msg.Dst_len == 0 {
// Ignore default routes
continue
}
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return nil, err
}
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.RTA_DST:
ip := attr.Value
ipNet = &net.IPNet{
IP: ip,
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)),
}
case syscall.RTA_OIF:
index := int(native.Uint32(attr.Value[0:4]))
iface, _ = net.InterfaceByIndex(index)
_ = iface
}
}
if ipNet != nil {
res = append(res, ipNet)
}
}
}
return res, nil
}
+141 -208
View File
@@ -4,17 +4,16 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/dotcloud/docker/iptables"
"github.com/dotcloud/docker/netlink"
"github.com/dotcloud/docker/proxy"
"github.com/dotcloud/docker/utils"
"log"
"net"
"os/exec"
"strconv"
"strings"
"sync"
)
var NetworkBridgeIface string
const (
DefaultNetworkBridge = "docker0"
DisableNetworkBridge = "none"
@@ -68,54 +67,10 @@ func networkSize(mask net.IPMask) int32 {
return int32(binary.BigEndian.Uint32(m)) + 1
}
//Wrapper around the ip command
func ip(args ...string) (string, error) {
path, err := exec.LookPath("ip")
if err != nil {
return "", fmt.Errorf("command not found: ip")
}
output, err := exec.Command(path, args...).CombinedOutput()
if err != nil {
return "", fmt.Errorf("ip failed: ip %v", strings.Join(args, " "))
}
return string(output), nil
}
// Wrapper around the iptables command
func iptables(args ...string) error {
path, err := exec.LookPath("iptables")
if err != nil {
return fmt.Errorf("command not found: iptables")
}
if err := exec.Command(path, args...).Run(); err != nil {
return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
}
return nil
}
func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error {
utils.Debugf("Routes:\n\n%s", routes)
for _, line := range strings.Split(routes, "\n") {
if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") {
continue
}
_, network, err := net.ParseCIDR(strings.Split(line, " ")[0])
if err != nil {
// is this a mask-less IP address?
if ip := net.ParseIP(strings.Split(line, " ")[0]); ip == nil {
// fail only if it's neither a network nor a mask-less IP address
return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line)
} else {
_, network, err = net.ParseCIDR(ip.String() + "/32")
if err != nil {
return err
}
}
}
if err == nil && network != nil {
if networkOverlaps(dockerNetwork, network) {
return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, line)
}
func checkRouteOverlaps(networks []*net.IPNet, dockerNetwork *net.IPNet) error {
for _, network := range networks {
if networkOverlaps(dockerNetwork, network) {
return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network)
}
}
return nil
@@ -124,7 +79,7 @@ func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error {
// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`,
// and attempts to configure it with an address which doesn't conflict with any other interface on the host.
// If it can't find an address which doesn't conflict, it will return an error.
func CreateBridgeIface(ifaceName string) error {
func CreateBridgeIface(config *DaemonConfig) error {
addrs := []string{
// Here we don't follow the convention of using the 1st IP of the range for the gateway.
// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
@@ -151,7 +106,7 @@ func CreateBridgeIface(ifaceName string) error {
if err != nil {
return err
}
routes, err := ip("route")
routes, err := netlink.NetworkGetRoutes()
if err != nil {
return err
}
@@ -163,23 +118,43 @@ func CreateBridgeIface(ifaceName string) error {
}
}
if ifaceAddr == "" {
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface)
}
utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr)
if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
if err := netlink.NetworkLinkAdd(config.BridgeIface, "bridge"); err != nil {
return fmt.Errorf("Error creating bridge: %s", err)
}
iface, err := net.InterfaceByName(config.BridgeIface)
if err != nil {
return err
}
ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr)
if err != nil {
return err
}
if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil {
return fmt.Errorf("Unable to add private network: %s", err)
}
if err := netlink.NetworkLinkUp(iface); err != nil {
return fmt.Errorf("Unable to start network bridge: %s", err)
}
if output, err := ip("addr", "add", ifaceAddr, "dev", ifaceName); err != nil {
return fmt.Errorf("Unable to add private network: %s (%s)", err, output)
}
if output, err := ip("link", "set", ifaceName, "up"); err != nil {
return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output)
}
if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
if config.EnableIptables {
if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
}
if !config.InterContainerCommunication {
utils.Debugf("Disable inter-container communication")
if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil {
return fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
}
} else {
utils.Debugf("Enable inter-container communication")
iptables.Raw("-D", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP")
}
}
return nil
}
@@ -216,58 +191,27 @@ func getIfaceAddr(name string) (net.Addr, error) {
// It keeps track of all mappings and is able to unmap at will
type PortMapper struct {
tcpMapping map[int]*net.TCPAddr
tcpProxies map[int]Proxy
tcpProxies map[int]proxy.Proxy
udpMapping map[int]*net.UDPAddr
udpProxies map[int]Proxy
udpProxies map[int]proxy.Proxy
iptables *iptables.Chain
defaultIp net.IP
}
func (mapper *PortMapper) cleanup() error {
// Ignore errors - This could mean the chains were never set up
iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER")
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6
// Also cleanup rules created by older versions, or -X might fail.
iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
iptables("-t", "nat", "-F", "DOCKER")
iptables("-t", "nat", "-X", "DOCKER")
mapper.tcpMapping = make(map[int]*net.TCPAddr)
mapper.tcpProxies = make(map[int]Proxy)
mapper.udpMapping = make(map[int]*net.UDPAddr)
mapper.udpProxies = make(map[int]Proxy)
return nil
}
func (mapper *PortMapper) setup() error {
if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
return fmt.Errorf("Failed to create DOCKER chain: %s", err)
}
if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
}
if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil {
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
return nil
}
func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error {
return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port),
"!", "-i", NetworkBridgeIface,
"-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
}
func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error {
if _, isTCP := backendAddr.(*net.TCPAddr); isTCP {
backendPort := backendAddr.(*net.TCPAddr).Port
backendIP := backendAddr.(*net.TCPAddr).IP
if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil {
return err
if mapper.iptables != nil {
if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil {
return err
}
}
mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr)
proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr)
proxy, err := proxy.NewProxy(&net.TCPAddr{IP: ip, Port: port}, backendAddr)
if err != nil {
mapper.Unmap(port, "tcp")
mapper.Unmap(ip, port, "tcp")
return err
}
mapper.tcpProxies[port] = proxy
@@ -275,13 +219,15 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
} else {
backendPort := backendAddr.(*net.UDPAddr).Port
backendIP := backendAddr.(*net.UDPAddr).IP
if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil {
return err
if mapper.iptables != nil {
if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil {
return err
}
}
mapper.udpMapping[port] = backendAddr.(*net.UDPAddr)
proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr)
proxy, err := proxy.NewProxy(&net.UDPAddr{IP: ip, Port: port}, backendAddr)
if err != nil {
mapper.Unmap(port, "udp")
mapper.Unmap(ip, port, "udp")
return err
}
mapper.udpProxies[port] = proxy
@@ -290,7 +236,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
return nil
}
func (mapper *PortMapper) Unmap(port int, proto string) error {
func (mapper *PortMapper) Unmap(ip net.IP, port int, proto string) error {
if proto == "tcp" {
backendAddr, ok := mapper.tcpMapping[port]
if !ok {
@@ -300,8 +246,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
proxy.Close()
delete(mapper.tcpProxies, port)
}
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
if mapper.iptables != nil {
if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
}
}
delete(mapper.tcpMapping, port)
} else {
@@ -313,21 +261,37 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
proxy.Close()
delete(mapper.udpProxies, port)
}
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
if mapper.iptables != nil {
if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
}
}
delete(mapper.udpMapping, port)
}
return nil
}
func newPortMapper() (*PortMapper, error) {
mapper := &PortMapper{}
if err := mapper.cleanup(); err != nil {
func newPortMapper(config *DaemonConfig) (*PortMapper, error) {
// We can always try removing the iptables
if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
return nil, err
}
if err := mapper.setup(); err != nil {
return nil, err
var chain *iptables.Chain
if config.EnableIptables {
var err error
chain, err = iptables.NewChain("DOCKER", config.BridgeIface)
if err != nil {
return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err)
}
}
mapper := &PortMapper{
tcpMapping: make(map[int]*net.TCPAddr),
tcpProxies: make(map[int]proxy.Proxy),
udpMapping: make(map[int]*net.UDPAddr),
udpProxies: make(map[int]proxy.Proxy),
iptables: chain,
defaultIp: config.DefaultIp,
}
return mapper, nil
}
@@ -519,40 +483,56 @@ type NetworkInterface struct {
disabled bool
}
// Allocate an external TCP port and map it to the interface
func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
// Allocate an external port and map it to the interface
func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) {
if iface.disabled {
return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME
}
nat, err := parseNat(spec)
ip := iface.manager.portMapper.defaultIp
if binding.HostIp != "" {
ip = net.ParseIP(binding.HostIp)
} else {
binding.HostIp = ip.String()
}
nat := &Nat{
Port: port,
Binding: binding,
}
containerPort, err := parsePort(port.Port())
if err != nil {
return nil, err
}
if nat.Proto == "tcp" {
extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend)
hostPort, _ := parsePort(nat.Binding.HostPort)
if nat.Port.Proto() == "tcp" {
extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort)
if err != nil {
return nil, err
}
backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort}
if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil {
iface.manager.tcpPortAllocator.Release(extPort)
return nil, err
}
nat.Frontend = extPort
nat.Binding.HostPort = strconv.Itoa(extPort)
} else {
extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend)
extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort)
if err != nil {
return nil, err
}
backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort}
if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil {
iface.manager.udpPortAllocator.Release(extPort)
return nil, err
}
nat.Frontend = extPort
nat.Binding.HostPort = strconv.Itoa(extPort)
}
iface.extPorts = append(iface.extPorts, nat)
@@ -560,83 +540,37 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
}
type Nat struct {
Proto string
Frontend int
Backend int
Port Port
Binding PortBinding
}
func parseNat(spec string) (*Nat, error) {
var nat Nat
if strings.Contains(spec, "/") {
specParts := strings.Split(spec, "/")
if len(specParts) != 2 {
return nil, fmt.Errorf("Invalid port format.")
}
proto := specParts[1]
spec = specParts[0]
if proto != "tcp" && proto != "udp" {
return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto)
}
nat.Proto = proto
} else {
nat.Proto = "tcp"
}
if strings.Contains(spec, ":") {
specParts := strings.Split(spec, ":")
if len(specParts) != 2 {
return nil, fmt.Errorf("Invalid port format.")
}
// If spec starts with ':', external and internal ports must be the same.
// This might fail if the requested external port is not available.
var sameFrontend bool
if len(specParts[0]) == 0 {
sameFrontend = true
} else {
front, err := strconv.ParseUint(specParts[0], 10, 16)
if err != nil {
return nil, err
}
nat.Frontend = int(front)
}
back, err := strconv.ParseUint(specParts[1], 10, 16)
if err != nil {
return nil, err
}
nat.Backend = int(back)
if sameFrontend {
nat.Frontend = nat.Backend
}
} else {
port, err := strconv.ParseUint(spec, 10, 16)
if err != nil {
return nil, err
}
nat.Backend = int(port)
}
return &nat, nil
func (n *Nat) String() string {
return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto())
}
// Release: Network cleanup - release all resources
func (iface *NetworkInterface) Release() {
if iface.disabled {
return
}
for _, nat := range iface.extPorts {
utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend)
if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil {
log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err)
hostPort, err := parsePort(nat.Binding.HostPort)
if err != nil {
log.Printf("Unable to get host port: %s", err)
continue
}
if nat.Proto == "tcp" {
if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil {
log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err)
ip := net.ParseIP(nat.Binding.HostIp)
utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort)
if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil {
log.Printf("Unable to unmap port %s: %s", nat, err)
}
if nat.Port.Proto() == "tcp" {
if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil {
log.Printf("Unable to release port %s", nat)
}
} else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil {
log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err)
} else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil {
log.Printf("Unable to release port %s: %s", nat, err)
}
}
@@ -704,22 +638,21 @@ func (manager *NetworkManager) Close() error {
return err3
}
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
if bridgeIface == DisableNetworkBridge {
func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) {
if config.BridgeIface == DisableNetworkBridge {
manager := &NetworkManager{
disabled: true,
}
return manager, nil
}
addr, err := getIfaceAddr(bridgeIface)
addr, err := getIfaceAddr(config.BridgeIface)
if err != nil {
// If the iface is not found, try to create it
if err := CreateBridgeIface(bridgeIface); err != nil {
if err := CreateBridgeIface(config); err != nil {
return nil, err
}
addr, err = getIfaceAddr(bridgeIface)
addr, err = getIfaceAddr(config.BridgeIface)
if err != nil {
return nil, err
}
@@ -737,13 +670,13 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
return nil, err
}
portMapper, err := newPortMapper()
portMapper, err := newPortMapper(config)
if err != nil {
return nil, err
}
manager := &NetworkManager{
bridgeIface: bridgeIface,
bridgeIface: config.BridgeIface,
bridgeNetwork: network,
ipAllocator: ipAllocator,
tcpPortAllocator: tcpPortAllocator,
+7 -114
View File
@@ -2,117 +2,9 @@ package docker
import (
"net"
"os"
"testing"
)
func TestIptables(t *testing.T) {
if err := iptables("-L"); err != nil {
t.Fatal(err)
}
path := os.Getenv("PATH")
os.Setenv("PATH", "")
defer os.Setenv("PATH", path)
if err := iptables("-L"); err == nil {
t.Fatal("Not finding iptables in the PATH should cause an error")
}
}
func TestParseNat(t *testing.T) {
if nat, err := parseNat("4500"); err == nil {
if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" {
t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat(":4501"); err == nil {
if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" {
t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4502:4503"); err == nil {
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4502:4503/tcp"); err == nil {
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4502:4503/udp"); err == nil {
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" {
t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat(":4503/udp"); err == nil {
if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" {
t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat(":4503/tcp"); err == nil {
if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4503/tcp"); err == nil {
if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4503/udp"); err == nil {
if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" {
t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if _, err := parseNat("4503/tcpgarbage"); err == nil {
t.Fatal(err)
}
if _, err := parseNat("4503/tcp/udp"); err == nil {
t.Fatal(err)
}
if _, err := parseNat("4503/"); err == nil {
t.Fatal(err)
}
}
func TestPortAllocation(t *testing.T) {
allocator, err := newPortAllocator()
if err != nil {
@@ -385,12 +277,13 @@ func TestNetworkOverlaps(t *testing.T) {
}
func TestCheckRouteOverlaps(t *testing.T) {
routes := `default via 10.0.2.2 dev eth0
10.0.2.0 dev eth0 proto kernel scope link src 10.0.2.15
10.0.3.0/24 dev lxcbr0 proto kernel scope link src 10.0.3.1
10.0.42.0/24 dev testdockbr0 proto kernel scope link src 10.0.42.1
172.16.42.0/24 dev docker0 proto kernel scope link src 172.16.42.1
192.168.142.0/24 dev eth1 proto kernel scope link src 192.168.142.142`
routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"}
routes := []*net.IPNet{}
for _, addr := range routesData {
_, netX, _ := net.ParseCIDR(addr)
routes = append(routes, netX)
}
_, netX, _ := net.ParseCIDR("172.16.0.1/24")
if err := checkRouteOverlaps(routes, netX); err != nil {
+1
View File
@@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
@@ -1,4 +1,4 @@
package docker
package proxy
import (
"bytes"
+29
View File
@@ -0,0 +1,29 @@
package proxy
import (
"fmt"
"net"
)
type Proxy interface {
// Start forwarding traffic back and forth the front and back-end
// addresses.
Run()
// Stop forwarding traffic and close both ends of the Proxy.
Close()
// Return the address on which the proxy is listening.
FrontendAddr() net.Addr
// Return the proxied address.
BackendAddr() net.Addr
}
func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
switch frontendAddr.(type) {
case *net.UDPAddr:
return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
case *net.TCPAddr:
return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
default:
panic(fmt.Errorf("Unsupported protocol"))
}
}
+93
View File
@@ -0,0 +1,93 @@
package proxy
import (
"github.com/dotcloud/docker/utils"
"io"
"log"
"net"
"syscall"
)
type TCPProxy struct {
listener *net.TCPListener
frontendAddr *net.TCPAddr
backendAddr *net.TCPAddr
}
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
listener, err := net.ListenTCP("tcp", frontendAddr)
if err != nil {
return nil, err
}
// If the port in frontendAddr was 0 then ListenTCP will have a picked
// a port to listen on, hence the call to Addr to get that actual port:
return &TCPProxy{
listener: listener,
frontendAddr: listener.Addr().(*net.TCPAddr),
backendAddr: backendAddr,
}, nil
}
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
if err != nil {
log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error())
client.Close()
return
}
event := make(chan int64)
var broker = func(to, from *net.TCPConn) {
written, err := io.Copy(to, from)
if err != nil {
// If the socket we are writing to is shutdown with
// SHUT_WR, forward it to the other end of the pipe:
if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
from.CloseWrite()
}
}
to.CloseRead()
event <- written
}
utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr())
go broker(client, backend)
go broker(backend, client)
var transferred int64 = 0
for i := 0; i < 2; i++ {
select {
case written := <-event:
transferred += written
case <-quit:
// Interrupt the two brokers and "join" them.
client.Close()
backend.Close()
for ; i < 2; i++ {
transferred += <-event
}
goto done
}
}
client.Close()
backend.Close()
done:
utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr())
}
func (proxy *TCPProxy) Run() {
quit := make(chan bool)
defer close(quit)
utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr)
for {
client, err := proxy.listener.Accept()
if err != nil {
utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error())
return
}
go proxy.clientLoop(client.(*net.TCPConn), quit)
}
}
func (proxy *TCPProxy) Close() { proxy.listener.Close() }
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
+1 -115
View File
@@ -1,10 +1,8 @@
package docker
package proxy
import (
"encoding/binary"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"log"
"net"
"sync"
@@ -17,107 +15,6 @@ const (
UDPBufSize = 2048
)
type Proxy interface {
// Start forwarding traffic back and forth the front and back-end
// addresses.
Run()
// Stop forwarding traffic and close both ends of the Proxy.
Close()
// Return the address on which the proxy is listening.
FrontendAddr() net.Addr
// Return the proxied address.
BackendAddr() net.Addr
}
type TCPProxy struct {
listener *net.TCPListener
frontendAddr *net.TCPAddr
backendAddr *net.TCPAddr
}
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
listener, err := net.ListenTCP("tcp", frontendAddr)
if err != nil {
return nil, err
}
// If the port in frontendAddr was 0 then ListenTCP will have a picked
// a port to listen on, hence the call to Addr to get that actual port:
return &TCPProxy{
listener: listener,
frontendAddr: listener.Addr().(*net.TCPAddr),
backendAddr: backendAddr,
}, nil
}
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
if err != nil {
log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error())
client.Close()
return
}
event := make(chan int64)
var broker = func(to, from *net.TCPConn) {
written, err := io.Copy(to, from)
if err != nil {
// If the socket we are writing to is shutdown with
// SHUT_WR, forward it to the other end of the pipe:
if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
from.CloseWrite()
}
}
to.CloseRead()
event <- written
}
utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr())
go broker(client, backend)
go broker(backend, client)
var transferred int64 = 0
for i := 0; i < 2; i++ {
select {
case written := <-event:
transferred += written
case <-quit:
// Interrupt the two brokers and "join" them.
client.Close()
backend.Close()
for ; i < 2; i++ {
transferred += <-event
}
goto done
}
}
client.Close()
backend.Close()
done:
utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr())
}
func (proxy *TCPProxy) Run() {
quit := make(chan bool)
defer close(quit)
utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr)
for {
client, err := proxy.listener.Accept()
if err != nil {
if utils.IsClosedError(err) {
utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (socket was closed)", proxy.frontendAddr, proxy.backendAddr)
} else {
utils.Errorf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error())
}
return
}
go proxy.clientLoop(client.(*net.TCPConn), quit)
}
}
func (proxy *TCPProxy) Close() { proxy.listener.Close() }
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
// A net.Addr where the IP is split into two fields so you can use it as a key
// in a map:
type connTrackKey struct {
@@ -253,14 +150,3 @@ func (proxy *UDPProxy) Close() {
func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
switch frontendAddr.(type) {
case *net.UDPAddr:
return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
case *net.TCPAddr:
return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
default:
panic(fmt.Errorf("Unsupported protocol"))
}
}
+309 -45
View File
@@ -3,6 +3,8 @@ package docker
import (
"container/list"
"fmt"
"github.com/dotcloud/docker/devmapper"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -10,11 +12,16 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
const (
DefaultFilesystemType = "devicemapper"
)
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
type Capabilities struct {
@@ -24,7 +31,6 @@ type Capabilities struct {
}
type Runtime struct {
root string
repository string
containers *list.List
networkManager *NetworkManager
@@ -33,16 +39,33 @@ type Runtime struct {
idIndex *utils.TruncIndex
capabilities *Capabilities
kernelVersion *utils.KernelVersionInfo
autoRestart bool
volumes *Graph
srv *Server
Dns []string
deviceSet *devmapper.DeviceSet
config *DaemonConfig
containerGraph *gograph.Database
}
var sysInitPath string
func init() {
sysInitPath = utils.SelfPath()
env := os.Getenv("_DOCKER_INIT_PATH")
if env != "" {
sysInitPath = env
} else {
selfPath := utils.SelfPath()
// If we have a separate dockerinit, use that, otherwise use the
// main docker binary
dir := filepath.Dir(selfPath)
dockerInitPath := filepath.Join(dir, "dockerinit")
if _, err := os.Stat(dockerInitPath); err != nil {
sysInitPath = selfPath
} else {
sysInitPath = dockerInitPath
}
}
}
// List returns an array of all containers registered in the runtime.
@@ -64,13 +87,28 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element {
return nil
}
func (runtime *Runtime) GetDeviceSet() (*devmapper.DeviceSet, error) {
if runtime.deviceSet == nil {
return nil, fmt.Errorf("No device set available")
}
return runtime.deviceSet, nil
}
// Get looks for a container by the specified ID or name, and returns it.
// If the container is not found, or if an error occurs, nil is returned.
func (runtime *Runtime) Get(name string) *Container {
if name[0] != '/' {
name = "/" + name
}
if c, _ := runtime.GetByName(name); c != nil {
return c
}
id, err := runtime.idIndex.Get(name)
if err != nil {
return nil
}
e := runtime.getContainerElement(id)
if e == nil {
return nil
@@ -88,10 +126,9 @@ func (runtime *Runtime) containerRoot(id string) string {
return path.Join(runtime.repository, id)
}
// Load reads the contents of a container from disk and registers
// it with Register.
// Load reads the contents of a container from disk
// This is typically done at startup.
func (runtime *Runtime) Load(id string) (*Container, error) {
func (runtime *Runtime) load(id string) (*Container, error) {
container := &Container{root: runtime.containerRoot(id)}
if err := container.FromDisk(); err != nil {
return nil, err
@@ -102,9 +139,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if container.State.Running {
container.State.Ghost = true
}
if err := runtime.Register(container); err != nil {
return nil, err
}
return container, nil
}
@@ -149,11 +183,11 @@ func (runtime *Runtime) Register(container *Container) error {
}
if !strings.Contains(string(output), "RUNNING") {
utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
if runtime.autoRestart {
if runtime.config.AutoRestart {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
hostConfig := &HostConfig{}
hostConfig, _ := container.ReadHostConfig()
if err := container.Start(hostConfig); err != nil {
return err
}
@@ -173,9 +207,9 @@ func (runtime *Runtime) Register(container *Container) error {
if !container.State.Running {
close(container.waitLock)
} else if !nomonitor {
container.allocateNetwork()
// hostConfig isn't needed here and can be nil
go container.monitor(nil)
hostConfig, _ := container.ReadHostConfig()
container.allocateNetwork(hostConfig)
go container.monitor(hostConfig)
}
return nil
}
@@ -203,6 +237,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
if err := container.Stop(3); err != nil {
return err
}
if mounted, err := container.Mounted(); err != nil {
return err
} else if mounted {
@@ -210,12 +245,35 @@ func (runtime *Runtime) Destroy(container *Container) error {
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err)
}
}
if _, err := runtime.containerGraph.Purge(container.ID); err != nil {
utils.Debugf("Unable to remove container from link graph: %s", err)
}
// Deregister the container before removing its directory, to avoid race conditions
runtime.idIndex.Delete(container.ID)
runtime.containers.Remove(element)
if err := os.RemoveAll(container.root); err != nil {
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
}
if runtime.deviceSet.HasDevice(container.ID) {
if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil {
return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err)
}
}
return nil
}
func (runtime *Runtime) DeleteImage(id string) error {
err := runtime.graph.Delete(id)
if err != nil {
return err
}
if runtime.deviceSet.HasDevice(id) {
if err := runtime.deviceSet.RemoveDevice(id); err != nil {
return fmt.Errorf("Unable to remove device for %v: %v", id, err)
}
}
return nil
}
@@ -228,9 +286,15 @@ func (runtime *Runtime) restore() error {
if err != nil {
return err
}
var (
containers []*Container
containersToMigrate []*Container
)
for i, v := range dir {
id := v.Name()
container, err := runtime.Load(id)
container, err := runtime.load(id)
if i%21 == 0 && os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
fmt.Printf("\b%c", wheel[i%4])
}
@@ -239,10 +303,110 @@ func (runtime *Runtime) restore() error {
continue
}
utils.Debugf("Loaded container %v", container.ID)
containers = append(containers, container)
if container.FilesystemType != DefaultFilesystemType {
containersToMigrate = append(containersToMigrate, container)
}
}
// Migrate containers to the default filesystem type
if len(containersToMigrate) > 0 {
if err := migrateToDeviceMapper(runtime, containersToMigrate); err != nil {
return err
}
}
sortContainers(containers, func(i, j *Container) bool {
ic, _ := i.ReadHostConfig()
jc, _ := j.ReadHostConfig()
if ic == nil || ic.Links == nil {
return true
}
if jc == nil || jc.Links == nil {
return false
}
return len(ic.Links) < len(jc.Links)
})
for _, container := range containers {
if err := runtime.Register(container); err != nil {
utils.Debugf("Failed to register container %s: %s", container.ID, err)
continue
}
}
if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
fmt.Printf("\bdone.\n")
}
return nil
}
func migrateToDeviceMapper(runtime *Runtime, containers []*Container) error {
var (
image *Image
contents []os.FileInfo
err error
)
fmt.Printf("Migrating %d containers to new storage backend\n", len(containers))
for _, container := range containers {
if container.State.Running {
fmt.Printf("WARNING - Cannot migrate %s because the container is running. Please stop the container and relaunch the daemon!")
continue
}
fmt.Printf("Migrating %s\n", container.ID)
if contents, err = ioutil.ReadDir(container.rwPath()); err != nil {
if !os.IsNotExist(err) {
fmt.Printf("Error reading rw dir %s\n", err)
}
continue
}
if len(contents) == 0 {
fmt.Printf("Skipping migration of %s because rw layer contains no changes\n")
continue
}
if image, err = runtime.graph.Get(container.Image); err != nil {
fmt.Printf("Failed to fetch image %s\n", err)
continue
}
unmount := func() {
if err = image.Unmount(runtime, container.RootfsPath(), container.ID); err != nil {
fmt.Printf("Failed to unmount image %s\n", err)
}
}
if err = image.Mount(runtime, container.RootfsPath(), container.rwPath(), container.ID); err != nil {
fmt.Printf("Failed to mount image %s\n", err)
continue
}
if err = image.applyLayer(container.rwPath(), container.RootfsPath()); err != nil {
fmt.Printf("Failed to apply layer in storage backend %s\n", err)
unmount()
continue
}
unmount()
if err = os.RemoveAll(container.rwPath()); err != nil {
fmt.Printf("Failed to remove rw layer %s\n", err)
}
container.FilesystemType = DefaultFilesystemType
if err := container.ToDisk(); err != nil {
fmt.Printf("Failed to save filesystem type to disk %s\n", err)
}
fmt.Printf("Successful migration for %s\n", container.ID)
}
fmt.Printf("Migration complete\n")
return nil
}
@@ -275,27 +439,44 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
}
// Create creates a new container from the given configuration.
func (runtime *Runtime) Create(config *Config) (*Container, error) {
func (runtime *Runtime) Create(config *Config) (*Container, []string, error) {
// Lookup image
img, err := runtime.repositories.LookupImage(config.Image)
if err != nil {
return nil, err
return nil, nil, err
}
warnings := []string{}
if img.Config != nil {
if img.Config.PortSpecs != nil && warnings != nil {
for _, p := range img.Config.PortSpecs {
if strings.Contains(p, ":") {
warnings = append(warnings, "This image expects private ports to be mapped to public ports on your host. "+
"This has been deprecated and the public mappings will not be honored."+
"Use -p to publish the ports.")
break
}
}
}
if err := MergeConfig(config, img.Config); err != nil {
return nil, err
return nil, nil, err
}
}
if len(config.Entrypoint) != 0 && config.Cmd == nil {
config.Cmd = []string{}
} else if config.Cmd == nil || len(config.Cmd) == 0 {
return nil, fmt.Errorf("No command specified")
return nil, nil, fmt.Errorf("No command specified")
}
// Generate id
id := GenerateID()
// Set the default enitity in the graph
if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", id), id); err != nil {
return nil, nil, err
}
// Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" {
@@ -323,42 +504,43 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath: sysInitPath,
SysInitPath: sysInitPath,
FilesystemType: DefaultFilesystemType,
}
container.root = runtime.containerRoot(container.ID)
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil {
return nil, err
return nil, nil, err
}
resolvConf, err := utils.GetResolvConf()
if err != nil {
return nil, err
return nil, nil, err
}
if len(config.Dns) == 0 && len(runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
runtime.Dns = defaultDns
runtime.config.Dns = defaultDns
}
// If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 || len(runtime.Dns) > 0 {
if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 {
var dns []string
if len(config.Dns) > 0 {
dns = config.Dns
} else {
dns = runtime.Dns
dns = runtime.config.Dns
}
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
f, err := os.Create(container.ResolvConfPath)
if err != nil {
return nil, err
return nil, nil, err
}
defer f.Close()
for _, dns := range dns {
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
return nil, err
return nil, nil, err
}
}
} else {
@@ -367,7 +549,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
// Step 2: save the container json
if err := container.ToDisk(); err != nil {
return nil, err
return nil, nil, err
}
// Step 3: if hostname, build hostname and hosts files
@@ -397,9 +579,9 @@ ff02::2 ip6-allrouters
// Step 4: register the container
if err := runtime.Register(container); err != nil {
return nil, err
return nil, nil, err
}
return container, nil
return container, warnings, nil
}
// Commit creates a new filesystem image from the current state of a container.
@@ -429,13 +611,85 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a
return img, nil
}
// FIXME: harmonize with NewGraph()
func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart)
func (runtime *Runtime) GetByName(name string) (*Container, error) {
if id, err := runtime.idIndex.Get(name); err == nil {
name = id
}
entity := runtime.containerGraph.Get(name)
if entity == nil {
return nil, fmt.Errorf("Could not find entity for %s", name)
}
e := runtime.getContainerElement(entity.ID())
if e == nil {
return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID())
}
return e.Value.(*Container), nil
}
func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
children := make(map[string]*Container)
err := runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error {
c := runtime.Get(e.ID())
if c == nil {
return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p)
}
children[p] = c
return nil
}, 0)
if err != nil {
return nil, err
}
return children, nil
}
func (runtime *Runtime) RenameLink(oldName, newName string) error {
if id, err := runtime.idIndex.Get(oldName); err == nil {
oldName = id
}
entity := runtime.containerGraph.Get(oldName)
if entity == nil {
return fmt.Errorf("Could not find entity for %s", oldName)
}
// This is not rename but adding a new link for the default name
// Strip the leading '/'
if strings.HasPrefix(entity.ID(), oldName[1:]) {
_, err := runtime.containerGraph.Set(newName, entity.ID())
return err
}
return runtime.containerGraph.Rename(oldName, newName)
}
func (runtime *Runtime) Link(parentName, childName, alias string) error {
if id, err := runtime.idIndex.Get(parentName); err == nil {
parentName = id
}
parent := runtime.containerGraph.Get(parentName)
if parent == nil {
return fmt.Errorf("Could not get container for %s", parentName)
}
if id, err := runtime.idIndex.Get(childName); err == nil {
childName = id
}
child := runtime.containerGraph.Get(childName)
if child == nil {
return fmt.Errorf("Could not get container for %s", childName)
}
cc := runtime.Get(child.ID())
_, err := runtime.containerGraph.Set(path.Join(parentName, alias), cc.ID)
return err
}
// FIXME: harmonize with NewGraph()
func NewRuntime(config *DaemonConfig) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory(config)
if err != nil {
return nil, err
}
runtime.Dns = dns
if k, err := utils.GetKernelVersion(); err != nil {
log.Printf("WARNING: %s\n", err)
@@ -449,34 +703,42 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e
return runtime, nil
}
func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
runtimeRepo := path.Join(root, "containers")
func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
runtimeRepo := path.Join(config.GraphPath, "containers")
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
g, err := NewGraph(path.Join(root, "graph"))
g, err := NewGraph(path.Join(config.GraphPath, "graph"))
if err != nil {
return nil, err
}
volumes, err := NewGraph(path.Join(root, "volumes"))
volumes, err := NewGraph(path.Join(config.GraphPath, "volumes"))
if err != nil {
return nil, err
}
repositories, err := NewTagStore(path.Join(root, "repositories"), g)
repositories, err := NewTagStore(path.Join(config.GraphPath, "repositories"), g)
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
}
if NetworkBridgeIface == "" {
NetworkBridgeIface = DefaultNetworkBridge
if config.BridgeIface == "" {
config.BridgeIface = DefaultNetworkBridge
}
netManager, err := newNetworkManager(NetworkBridgeIface)
netManager, err := newNetworkManager(config)
if err != nil {
return nil, err
}
graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db"))
if err != nil {
return nil, err
}
// Initialize devicemapper deviceSet
deviceSet := devmapper.NewDeviceSet(config.GraphPath)
runtime := &Runtime{
root: root,
repository: runtimeRepo,
containers: list.New(),
networkManager: netManager,
@@ -484,8 +746,10 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
repositories: repositories,
idIndex: utils.NewTruncIndex(),
capabilities: &Capabilities{},
autoRestart: autoRestart,
volumes: volumes,
config: config,
containerGraph: graph,
deviceSet: deviceSet,
}
if err := runtime.restore(); err != nil {
+411 -42
View File
@@ -3,11 +3,16 @@ package docker
import (
"bytes"
"fmt"
"github.com/dotcloud/docker/devmapper"
"github.com/dotcloud/docker/sysinit"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"log"
"net"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
@@ -24,6 +29,10 @@ const (
unitTestStoreBase = "/var/lib/docker/unit-tests"
testDaemonAddr = "127.0.0.1:4270"
testDaemonProto = "tcp"
unitTestDMDataLoopbackSize = 209715200 // 200MB
unitTestDMMetaDataLoopbackSize = 104857600 // 100MB
unitTestDMBaseFsSize = 157286400 // 150MB
)
var (
@@ -43,7 +52,14 @@ func nuke(runtime *Runtime) error {
}
wg.Wait()
runtime.networkManager.Close()
return os.RemoveAll(runtime.root)
for _, container := range runtime.List() {
container.EnsureUnmounted()
}
if err := runtime.deviceSet.Shutdown(); err != nil {
utils.Debugf("Error shutting down devicemapper for runtime %s", runtime.config.GraphPath)
}
return os.RemoveAll(runtime.config.GraphPath)
}
func cleanup(runtime *Runtime) error {
@@ -57,7 +73,7 @@ func cleanup(runtime *Runtime) error {
}
for _, image := range images {
if image.ID != unitTestImageID {
runtime.graph.Delete(image.ID)
runtime.DeleteImage(image.ID)
}
}
return nil
@@ -72,12 +88,86 @@ func layerArchive(tarfile string) (io.Reader, error) {
return f, nil
}
// Remove any leftover device mapper devices from earlier runs of the unit tests
func removeDev(name string) error {
path := filepath.Join("/dev/mapper", name)
if f, err := os.OpenFile(path, os.O_RDONLY, 07777); err != nil {
if er, ok := err.(*os.PathError); ok && er.Err == syscall.ENXIO {
// No device for this node, just remove it
return os.Remove(path)
}
} else {
f.Close()
}
if err := devmapper.RemoveDevice(name); err != nil {
return fmt.Errorf("Unable to remove device %s needed to get a freash unit test environment", name)
}
return nil
}
func cleanupDevMapper() error {
utils.Debugf("[devmapper cleanup] starting")
defer utils.Debugf("[devmapper cleanup] done")
filter := "docker-" + path.Base(unitTestStoreBase)
utils.Debugf("Filtering out %s\n", filter)
// Unmount any leftover mounts from previous unit test runs
if data, err := ioutil.ReadFile("/proc/mounts"); err != nil {
return err
} else {
for _, line := range strings.Split(string(data), "\n") {
if cols := strings.Split(line, " "); len(cols) >= 2 && strings.HasPrefix(cols[0], path.Join("/dev/mapper", filter)) {
utils.Debugf("[devmapper cleanup] Unmounting device: %s", cols[1])
if err := syscall.Unmount(cols[1], 0); err != nil {
return fmt.Errorf("Unable to unmount %s needed to get a freash unit test environment: %s", cols[1], err)
}
}
}
}
utils.Debugf("[devmapper cleanup] looking for leftover devices")
// Remove any leftover devmapper devices from previous unit run tests
infos, err := ioutil.ReadDir("/dev/mapper")
if err != nil {
// If the mapper file does not exist there is nothing to clean up
if os.IsNotExist(err) {
return nil
}
return err
}
pools := []string{}
for _, info := range infos {
if name := info.Name(); strings.HasPrefix(name, filter+"-") {
if strings.HasSuffix(name, "-pool") {
pools = append(pools, name)
} else {
if err := removeDev(name); err != nil {
return err
}
}
}
}
// We need to remove the pool last as the other devices block it
for _, pool := range pools {
utils.Debugf("[devmapper cleanup] Removing pool: %s", pool)
if err := removeDev(pool); err != nil {
return err
}
}
return nil
}
func init() {
os.Setenv("TEST", "1")
// Set unit-test specific values
devmapper.DefaultDataLoopbackSize = unitTestDMDataLoopbackSize
devmapper.DefaultMetaDataLoopbackSize = unitTestDMMetaDataLoopbackSize
devmapper.DefaultBaseFsSize = unitTestDMBaseFsSize
// Hack to run sys init during unit testing
if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
SysInit()
sysinit.SysInit()
return
}
@@ -85,7 +175,9 @@ func init() {
log.Fatal("docker tests need to be run as root")
}
NetworkBridgeIface = unitTestNetworkBridge
if err := cleanupDevMapper(); err != nil {
log.Fatalf("Unable to cleanup devmapper: %s", err)
}
// Setup the base runtime, which will be duplicated for each test.
// (no tests are run directly in the base)
@@ -96,9 +188,13 @@ func init() {
startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine()
}
func setupBaseImage() {
runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
config := &DaemonConfig{
GraphPath: unitTestStoreBase,
AutoRestart: false,
BridgeIface: unitTestNetworkBridge,
}
runtime, err := NewRuntimeFromDirectory(config)
if err != nil {
log.Fatalf("Unable to create a runtime for tests:", err)
}
@@ -106,7 +202,6 @@ func setupBaseImage() {
// Create the "Server"
srv := &Server{
runtime: runtime,
enableCors: false,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
@@ -120,7 +215,6 @@ func setupBaseImage() {
}
}
func spawnGlobalDaemon() {
if globalRuntime != nil {
utils.Debugf("Global runtime already exists. Skipping.")
@@ -129,7 +223,6 @@ func spawnGlobalDaemon() {
globalRuntime = mkRuntime(log.New(os.Stderr, "", 0))
srv := &Server{
runtime: globalRuntime,
enableCors: false,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
@@ -171,7 +264,7 @@ func TestRuntimeCreate(t *testing.T) {
t.Errorf("Expected 0 containers, %v found", len(runtime.List()))
}
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
@@ -180,12 +273,6 @@ func TestRuntimeCreate(t *testing.T) {
t.Fatal(err)
}
defer func() {
if err := runtime.Destroy(container); err != nil {
t.Error(err)
}
}()
// Make sure we can find the newly created container with List()
if len(runtime.List()) != 1 {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
@@ -212,7 +299,7 @@ func TestRuntimeCreate(t *testing.T) {
}
// Make sure crete with bad parameters returns an error
_, err = runtime.Create(
_, _, err = runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
},
@@ -221,7 +308,7 @@ func TestRuntimeCreate(t *testing.T) {
t.Fatal("Builder.Create should throw an error when Cmd is missing")
}
_, err = runtime.Create(
_, _, err = runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{},
@@ -236,29 +323,18 @@ func TestRuntimeCreate(t *testing.T) {
Cmd: []string{"/bin/ls"},
PortSpecs: []string{"80"},
}
container, err = runtime.Create(config)
container, _, err = runtime.Create(config)
image, err := runtime.Commit(container, "testrepo", "testtag", "", "", config)
_, err = runtime.Commit(container, "testrepo", "testtag", "", "", config)
if err != nil {
t.Error(err)
}
_, err = runtime.Create(
&Config{
Image: image.ID,
PortSpecs: []string{"80000:80"},
},
)
if err == nil {
t.Fatal("Builder.Create should throw an error when PortSpecs is invalid")
}
}
func TestDestroy(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := runtime.Create(&Config{
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
@@ -332,6 +408,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
port := 5554
var container *Container
var strPort string
var p Port
for {
port += 1
strPort = strconv.Itoa(port)
@@ -344,22 +421,33 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
t.Fatal(fmt.Errorf("Unknown protocol %v", proto))
}
t.Log("Trying port", strPort)
container, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", cmd},
PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)},
ep := make(map[Port]struct{}, 1)
p = Port(fmt.Sprintf("%s/%s", strPort, proto))
ep[p] = struct{}{}
container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", cmd},
PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)},
ExposedPorts: ep,
})
if container != nil {
break
}
if err != nil {
nuke(runtime)
t.Fatal(err)
}
if container != nil {
break
}
t.Logf("Port %v already in use", strPort)
}
hostConfig := &HostConfig{}
hostConfig := &HostConfig{
PortBindings: make(map[Port][]PortBinding),
}
hostConfig.PortBindings[p] = []PortBinding{
{},
}
if err := container.Start(hostConfig); err != nil {
nuke(runtime)
t.Fatal(err)
@@ -374,7 +462,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
// Even if the state is running, lets give some time to lxc to spawn the process
container.WaitTimeout(500 * time.Millisecond)
strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort]
strPort = container.NetworkSettings.Ports[p][0].HostPort
return runtime, container, strPort
}
@@ -506,7 +594,8 @@ func TestRestore(t *testing.T) {
// Here are are simulating a docker restart - that is, reloading all containers
// from scratch
runtime2, err := NewRuntimeFromDirectory(runtime1.root, false)
runtime1.config.AutoRestart = false
runtime2, err := NewRuntimeFromDirectory(runtime1.config)
if err != nil {
t.Fatal(err)
}
@@ -533,3 +622,283 @@ func TestRestore(t *testing.T) {
}
container2.State.Running = false
}
func TestContainerCreatedWithDefaultFilesystemType(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
defer runtime.Destroy(container)
if container.FilesystemType != DefaultFilesystemType {
t.Fatalf("Container filesystem type should be %s but got %s", DefaultFilesystemType, container.FilesystemType)
}
}
func TestReloadContainerLinks(t *testing.T) {
runtime1 := mkRuntime(t)
defer nuke(runtime1)
// Create a container with one instance of docker
container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
defer runtime1.Destroy(container1)
// Create a second container meant to be killed
container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
defer runtime1.Destroy(container2)
// Start the container non blocking
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
h1 := &HostConfig{}
// Add a link to container 2
h1.Links = []string{utils.TruncateID(container2.ID) + ":first"}
if err := container1.Start(h1); err != nil {
t.Fatal(err)
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
if !container1.State.Running {
t.Fatalf("Container %s should appear as running bu isn't", container1.ID)
}
if len(runtime1.List()) != 2 {
t.Errorf("Expected 2 container, %v found", len(runtime1.List()))
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
// Here are are simulating a docker restart - that is, reloading all containers
// from scratch
runtime1.config.AutoRestart = true
runtime2, err := NewRuntimeFromDirectory(runtime1.config)
if err != nil {
t.Fatal(err)
}
defer nuke(runtime2)
if len(runtime2.List()) != 2 {
t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
}
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
t.Logf("Running container found: %v (%v)", c.ID, c.Path)
runningCount++
}
}
if runningCount != 2 {
t.Fatalf("Expected 2 container alive, %d found", runningCount)
}
// Make sure container 2 ( the child of container 1 ) was registered and started first
// with the runtime
first := runtime2.containers.Front()
if first.Value.(*Container).ID != container2.ID {
t.Fatalf("Container 2 %s should be registered first in the runtime", container2.ID)
}
t.Logf("Number of links: %d", runtime2.containerGraph.Refs("engine"))
// Verify that the link is still registered in the runtime
entity := runtime2.containerGraph.Get(fmt.Sprintf("/%s", container1.ID))
if entity == nil {
t.Fatal("Entity should not be nil")
}
}
func TestDefaultContainerName(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
containerID := container.ID
paths := runtime.containerGraph.RefPaths(containerID)
if paths == nil || len(paths) == 0 {
t.Fatalf("Could not find edges for %s", containerID)
}
edge := paths[0]
if edge.ParentID != "0" {
t.Fatalf("Expected engine got %s", edge.ParentID)
}
if edge.EntityID != containerID {
t.Fatalf("Expected %s got %s", containerID, edge.EntityID)
}
if edge.Name != containerID {
t.Fatalf("Expected %s got %s", containerID, edge.Name)
}
}
func TestDefaultContainerRename(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
containerID := container.ID
if err := runtime.RenameLink(fmt.Sprintf("/%s", containerID), "/webapp"); err != nil {
t.Fatal(err)
}
webapp, err := runtime.GetByName("/webapp")
if err != nil {
t.Fatal(err)
}
if webapp.ID != container.ID {
t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
}
}
func TestLinkChildContainer(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil {
t.Fatal(err)
}
webapp, err := runtime.GetByName("/webapp")
if err != nil {
t.Fatal(err)
}
if webapp.ID != container.ID {
t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
}
config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err = srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
childContainer := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil {
t.Fatal(err)
}
if err := runtime.Link("/webapp", "/db", "db"); err != nil {
t.Fatal(err)
}
// Get the child by it's new name
db, err := runtime.GetByName("/webapp/db")
if err != nil {
t.Fatal(err)
}
if db.ID != childContainer.ID {
t.Fatalf("Expect db id to match container id: %s != %s", db.ID, childContainer.ID)
}
}
func TestGetAllChildren(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil {
t.Fatal(err)
}
webapp, err := runtime.GetByName("/webapp")
if err != nil {
t.Fatal(err)
}
if webapp.ID != container.ID {
t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
}
config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err = srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
childContainer := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil {
t.Fatal(err)
}
if err := runtime.Link("/webapp", "/db", "db"); err != nil {
t.Fatal(err)
}
children, err := runtime.Children("/webapp")
if err != nil {
t.Fatal(err)
}
if children == nil {
t.Fatal("Children should not be nil")
}
if len(children) == 0 {
t.Fatal("Children should not be empty")
}
for key, value := range children {
if key != "/webapp/db" {
t.Fatalf("Expected /webapp/db got %s", key)
}
if value.ID != childContainer.ID {
t.Fatalf("Expected id %s got %s", childContainer.ID, value.ID)
}
}
}
+153 -60
View File
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils"
"io"
@@ -103,7 +104,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
}
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
r, err := registry.NewRegistry(srv.runtime.root, nil, srv.HTTPRequestFactory(nil))
r, err := registry.NewRegistry(srv.runtime.config.GraphPath, nil, srv.HTTPRequestFactory(nil))
if err != nil {
return nil, err
}
@@ -140,7 +141,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
return "", err
}
c, err := srv.runtime.Create(config)
c, _, err := srv.runtime.Create(config)
if err != nil {
return "", err
}
@@ -268,19 +269,26 @@ func (srv *Server) DockerInfo() *APIInfo {
kernelVersion = kv.String()
}
devSetInfo := srv.runtime.deviceSet.Status()
return &APIInfo{
Containers: len(srv.runtime.List()),
Images: imgcount,
MemoryLimit: srv.runtime.capabilities.MemoryLimit,
SwapLimit: srv.runtime.capabilities.SwapLimit,
IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled,
Debug: os.Getenv("DEBUG") != "",
NFd: utils.GetTotalUsedFds(),
NGoroutines: runtime.NumGoroutine(),
LXCVersion: lxcVersion,
NEventsListener: len(srv.events),
KernelVersion: kernelVersion,
IndexServerAddress: auth.IndexServerAddress(),
Containers: len(srv.runtime.List()),
Images: imgcount,
MemoryLimit: srv.runtime.capabilities.MemoryLimit,
SwapLimit: srv.runtime.capabilities.SwapLimit,
IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled,
Debug: os.Getenv("DEBUG") != "",
NFd: utils.GetTotalUsedFds(),
NGoroutines: runtime.NumGoroutine(),
LXCVersion: lxcVersion,
NEventsListener: len(srv.events),
KernelVersion: kernelVersion,
IndexServerAddress: auth.IndexServerAddress(),
DevmapperPool: devSetInfo.PoolName,
DevmapperDataUsed: devSetInfo.Data.Used,
DevmapperDataTotal: devSetInfo.Data.Total,
DevmapperMetadataUsed: devSetInfo.Metadata.Used,
DevmapperMetadataTotal: devSetInfo.Metadata.Total,
}
}
@@ -358,7 +366,7 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) {
func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers {
var foundBefore bool
var displayed int
retContainers := []APIContainers{}
out := []APIContainers{}
for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
@@ -380,23 +388,35 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
break
}
displayed++
c := APIContainers{
ID: container.ID,
}
c.Image = srv.runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingAPI()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
retContainers = append(retContainers, c)
c := createAPIContainer(container, size, srv.runtime)
out = append(out, c)
}
return retContainers
return out
}
func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
c := APIContainers{
ID: container.ID,
}
names := []string{}
runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
if e.ID() == container.ID {
names = append(names, p)
}
return nil
}, -1)
c.Names = names
c.Image = runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingAPI()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
return c
}
func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) {
container := srv.runtime.Get(name)
if container == nil {
@@ -635,7 +655,7 @@ func (srv *Server) poolRemove(kind, key string) error {
}
func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error {
r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders))
r, err := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders))
if err != nil {
return err
}
@@ -844,7 +864,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo
out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(localName)
r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders))
r, err2 := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders))
if err2 != nil {
return err2
}
@@ -909,10 +929,9 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
return nil
}
func (srv *Server) ContainerCreate(config *Config) (string, error) {
func (srv *Server) ContainerCreate(config *Config) (string, []string, error) {
if config.Memory != 0 && config.Memory < 524288 {
return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
@@ -922,7 +941,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
config.MemorySwap = -1
}
container, err := srv.runtime.Create(config)
container, buildWarnings, err := srv.runtime.Create(config)
if err != nil {
if srv.runtime.graph.IsNotExist(err) {
@@ -931,12 +950,12 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
tag = DEFAULTTAG
}
return "", fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
}
return "", err
return "", nil, err
}
srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
return container.ShortID(), nil
return container.ShortID(), buildWarnings, nil
}
func (srv *Server) ContainerRestart(name string, t int) error {
@@ -951,7 +970,34 @@ func (srv *Server) ContainerRestart(name string, t int) error {
return nil
}
func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) error {
if removeLink {
p := name
if p[0] != '/' {
p = "/" + p
}
parent, n := path.Split(p)
l := len(parent)
if parent[l-1] == '/' {
parent = parent[:l-1]
}
pe := srv.runtime.containerGraph.Get(parent)
parentContainer := srv.runtime.Get(pe.ID())
if parentContainer != nil && parentContainer.activeLinks != nil {
if link, exists := parentContainer.activeLinks[n]; exists {
link.Disable()
} else {
utils.Debugf("Could not find active link for %s", name)
}
}
if err := srv.runtime.containerGraph.Delete(name); err != nil {
return err
}
return nil
}
if container := srv.runtime.Get(name); container != nil {
if container.State.Running {
return fmt.Errorf("Impossible to remove a running container, please stop it first")
@@ -996,6 +1042,14 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
var ErrImageReferenced = errors.New("Image referenced by a repository")
func (srv *Server) getChildImages(id string) ([]*Image, error) {
byParents, err := srv.runtime.graph.ByParent()
if err != nil {
return nil, err
}
return byParents[id], nil
}
func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
// If the image is referenced by a repo, do not delete
if len(srv.runtime.repositories.ByID()[id]) != 0 {
@@ -1028,7 +1082,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
if err := srv.runtime.repositories.DeleteAll(id); err != nil {
return err
}
err := srv.runtime.graph.Delete(id)
err := srv.runtime.DeleteImage(id)
if err != nil {
return err
}
@@ -1101,8 +1155,35 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
if err != nil {
return nil, fmt.Errorf("No such image: %s", name)
}
images := make(map[string]bool)
images[img.ID] = true
children, err := srv.getChildImages(img.ID)
if err != nil {
return nil, err
}
for _, i := range children {
images[i.ID] = true
}
// Check for any containers referencing the image or children of the image
referencedContainers := []string{}
for e := srv.runtime.containers.Front(); e != nil; e = e.Next() {
c := e.Value.(*Container)
if images[c.Image] {
referencedContainers = append(referencedContainers, c.ID)
}
}
if len(referencedContainers) > 0 {
return nil, fmt.Errorf("Cannot delete image with existing containers. Please remove %s before deleting image.",
strings.Join(referencedContainers, ", "))
}
if !autoPrune {
if err := srv.runtime.graph.Delete(img.ID); err != nil {
if err := srv.runtime.DeleteImage(img.ID); err != nil {
return nil, fmt.Errorf("Error deleting image %s: %s", name, err)
}
return nil, nil
@@ -1143,14 +1224,32 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error)
}
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Start(hostConfig); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err)
}
srv.LogEvent("start", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
} else {
runtime := srv.runtime
container := runtime.Get(name)
if container == nil {
return fmt.Errorf("No such container: %s", name)
}
// Register links
if hostConfig != nil && hostConfig.Links != nil {
for _, l := range hostConfig.Links {
parts, err := parseLink(l)
if err != nil {
return err
}
childName := parts["name"]
if err := runtime.Link(fmt.Sprintf("/%s", container.ID), childName, parts["alias"]); err != nil {
return err
}
}
}
if err := container.Start(hostConfig); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err)
}
srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image))
return nil
}
@@ -1302,35 +1401,30 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er
}
func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
func NewServer(config *DaemonConfig) (*Server, error) {
if runtime.GOARCH != "amd64" {
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
}
runtime, err := NewRuntime(flGraphPath, autoRestart, dns)
runtime, err := NewRuntime(config)
if err != nil {
return nil, err
}
srv := &Server{
runtime.srv = &Server{
runtime: runtime,
enableCors: enableCors,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
listeners: make(map[string]chan utils.JSONMessage),
reqFactory: nil,
}
runtime.srv = srv
return srv, nil
return runtime.srv, nil
}
func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
if srv.reqFactory == nil {
ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...)
md := &utils.HTTPMetaHeadersDecorator{
Headers: metaHeaders,
}
factory := utils.NewHTTPRequestFactory(ud, md)
srv.reqFactory = factory
srv.reqFactory = utils.NewHTTPRequestFactory(
utils.NewHTTPUserAgentDecorator(srv.versionInfos()...),
&utils.HTTPMetaHeadersDecorator{Headers: metaHeaders})
}
return srv.reqFactory
}
@@ -1350,7 +1444,6 @@ func (srv *Server) LogEvent(action, id, from string) {
type Server struct {
sync.Mutex
runtime *Runtime
enableCors bool
pullingPool map[string]struct{}
pushingPool map[string]struct{}
events []utils.JSONMessage
+10 -10
View File
@@ -89,7 +89,7 @@ func TestCreateRm(t *testing.T) {
t.Fatal(err)
}
id, err := srv.ContainerCreate(config)
id, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
@@ -98,7 +98,7 @@ func TestCreateRm(t *testing.T) {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
if err = srv.ContainerDestroy(id, true); err != nil {
if err = srv.ContainerDestroy(id, true, false); err != nil {
t.Fatal(err)
}
@@ -119,7 +119,7 @@ func TestCreateRmVolumes(t *testing.T) {
t.Fatal(err)
}
id, err := srv.ContainerCreate(config)
id, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
@@ -138,7 +138,7 @@ func TestCreateRmVolumes(t *testing.T) {
t.Fatal(err)
}
if err = srv.ContainerDestroy(id, true); err != nil {
if err = srv.ContainerDestroy(id, true, false); err != nil {
t.Fatal(err)
}
@@ -158,7 +158,7 @@ func TestCommit(t *testing.T) {
t.Fatal(err)
}
id, err := srv.ContainerCreate(config)
id, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
@@ -179,7 +179,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Fatal(err)
}
id, err := srv.ContainerCreate(config)
id, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
@@ -214,7 +214,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
}
// FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty")
if err = srv.ContainerDestroy(id, true); err != nil {
if err = srv.ContainerDestroy(id, true, false); err != nil {
t.Fatal(err)
}
@@ -230,7 +230,7 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) {
srv := &Server{runtime: runtime}
defer nuke(runtime)
// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
_, err = srv.ContainerCreate(
_, _, err = srv.ContainerCreate(
&Config{
Image: GetTestImage(runtime).ID,
Memory: 524287,
@@ -402,7 +402,7 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
containerID, err := srv.ContainerCreate(config)
containerID, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
@@ -423,7 +423,7 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
containerID, err = srv.ContainerCreate(config)
containerID, _, err = srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
+69
View File
@@ -34,3 +34,72 @@ func sortImagesByCreationAndTag(images []APIImages) {
sort.Sort(sorter)
}
type portSorter struct {
ports []Port
by func(i, j Port) bool
}
func (s *portSorter) Len() int {
return len(s.ports)
}
func (s *portSorter) Swap(i, j int) {
s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
}
func (s *portSorter) Less(i, j int) bool {
ip := s.ports[i]
jp := s.ports[j]
return s.by(ip, jp)
}
func sortPorts(ports []Port, predicate func(i, j Port) bool) {
s := &portSorter{ports, predicate}
sort.Sort(s)
}
type containerSorter struct {
containers []*Container
by func(i, j *Container) bool
}
func (s *containerSorter) Len() int {
return len(s.containers)
}
func (s *containerSorter) Swap(i, j int) {
s.containers[i], s.containers[j] = s.containers[j], s.containers[i]
}
func (s *containerSorter) Less(i, j int) bool {
return s.by(s.containers[i], s.containers[j])
}
func sortContainers(containers []*Container, predicate func(i, j *Container) bool) {
s := &containerSorter{containers, predicate}
sort.Sort(s)
}
type apiLinkSorter struct {
links []APILink
by func(i, j APILink) bool
}
func (s *apiLinkSorter) Len() int {
return len(s.links)
}
func (s *apiLinkSorter) Swap(i, j int) {
s.links[i], s.links[j] = s.links[j], s.links[i]
}
func (s *apiLinkSorter) Less(i, j int) bool {
return s.by(s.links[i], s.links[j])
}
func sortLinks(links []APILink, predicate func(i, j APILink) bool) {
s := &apiLinkSorter{links, predicate}
sort.Sort(s)
}
+36
View File
@@ -1,6 +1,7 @@
package docker
import (
"fmt"
"testing"
)
@@ -55,3 +56,38 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) {
t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.")
}
}
func TestSortUniquePorts(t *testing.T) {
ports := []Port{
Port("6379/tcp"),
Port("22/tcp"),
}
sortPorts(ports, func(ip, jp Port) bool {
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
})
first := ports[0]
if fmt.Sprint(first) != "22/tcp" {
t.Log(fmt.Sprint(first))
t.Fail()
}
}
func TestSortSamePortWithDifferentProto(t *testing.T) {
ports := []Port{
Port("8888/tcp"),
Port("8888/udp"),
Port("6379/tcp"),
Port("6379/udp"),
}
sortPorts(ports, func(ip, jp Port) bool {
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
})
first := ports[0]
if fmt.Sprint(first) != "6379/tcp" {
t.Fail()
}
}
+5 -5
View File
@@ -9,12 +9,12 @@ import (
type State struct {
sync.Mutex
Running bool
Pid int
ExitCode int
StartedAt time.Time
Running bool
Pid int
ExitCode int
StartedAt time.Time
FinishedAt time.Time
Ghost bool
Ghost bool
}
// String returns a human-readable description of the state
+14 -5
View File
@@ -1,10 +1,12 @@
package docker
package sysinit
import (
"flag"
"fmt"
"github.com/dotcloud/docker/netlink"
"github.com/dotcloud/docker/utils"
"log"
"net"
"os"
"os/exec"
"strconv"
@@ -17,7 +19,14 @@ func setupNetworking(gw string) {
if gw == "" {
return
}
if _, err := ip("route", "add", "default", "via", gw); err != nil {
ip := net.ParseIP(gw)
if ip == nil {
log.Fatalf("Unable to set up networking, %s is not a valid IP", gw)
return
}
if err := netlink.AddDefaultGw(ip); err != nil {
log.Fatalf("Unable to set up networking: %v", err)
}
}
@@ -58,7 +67,7 @@ func changeUser(u string) {
}
// Clear environment pollution introduced by lxc-start
func cleanupEnv(env ListOpts) {
func cleanupEnv(env utils.ListOpts) {
os.Clearenv()
for _, kv := range env {
parts := strings.SplitN(kv, "=", 2)
@@ -86,14 +95,14 @@ func executeProgram(name string, args []string) {
// up the environment before running the actual process
func SysInit() {
if len(os.Args) <= 1 {
fmt.Println("You should not invoke docker-init manually")
fmt.Println("You should not invoke dockerinit manually")
os.Exit(1)
}
var u = flag.String("u", "", "username or uid")
var gw = flag.String("g", "", "gateway address")
var workdir = flag.String("w", "", "workdir")
var flEnv ListOpts
var flEnv utils.ListOpts
flag.Var(&flEnv, "e", "Set environment variables")
flag.Parse()
+193 -20
View File
@@ -1,8 +1,35 @@
package docker
/*
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <errno.h>
// See linux.git/fs/btrfs/ioctl.h
#define BTRFS_IOCTL_MAGIC 0x94
#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
int
btrfs_reflink(int fd_out, int fd_in)
{
int res;
res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in);
if (res < 0)
return errno;
return 0;
}
*/
import "C"
import (
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"syscall"
)
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
@@ -27,6 +54,7 @@ func CompareConfig(a, b *Config) bool {
len(a.Dns) != len(b.Dns) ||
len(a.Env) != len(b.Env) ||
len(a.PortSpecs) != len(b.PortSpecs) ||
len(a.ExposedPorts) != len(b.ExposedPorts) ||
len(a.Entrypoint) != len(b.Entrypoint) ||
len(a.Volumes) != len(b.Volumes) {
return false
@@ -52,6 +80,11 @@ func CompareConfig(a, b *Config) bool {
return false
}
}
for k := range a.ExposedPorts {
if _, exists := b.ExposedPorts[k]; !exists {
return false
}
}
for i := 0; i < len(a.Entrypoint); i++ {
if a.Entrypoint[i] != b.Entrypoint[i] {
return false
@@ -78,26 +111,38 @@ func MergeConfig(userConf, imageConf *Config) error {
if userConf.CpuShares == 0 {
userConf.CpuShares = imageConf.CpuShares
}
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
userConf.PortSpecs = imageConf.PortSpecs
} else {
for _, imagePortSpec := range imageConf.PortSpecs {
found := false
imageNat, err := parseNat(imagePortSpec)
if err != nil {
return err
if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
userConf.ExposedPorts = imageConf.ExposedPorts
}
if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
if userConf.ExposedPorts == nil {
userConf.ExposedPorts = make(map[Port]struct{})
}
ports, _, err := parsePortSpecs(userConf.PortSpecs)
if err != nil {
return err
}
for port := range ports {
if _, exists := userConf.ExposedPorts[port]; !exists {
userConf.ExposedPorts[port] = struct{}{}
}
for _, userPortSpec := range userConf.PortSpecs {
userNat, err := parseNat(userPortSpec)
if err != nil {
return err
}
if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend {
found = true
}
}
if !found {
userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec)
}
userConf.PortSpecs = nil
}
if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 {
utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", "))
if userConf.ExposedPorts == nil {
userConf.ExposedPorts = make(map[Port]struct{})
}
ports, _, err := parsePortSpecs(imageConf.PortSpecs)
if err != nil {
return err
}
for port := range ports {
if _, exists := userConf.ExposedPorts[port]; !exists {
userConf.ExposedPorts[port] = struct{}{}
}
}
}
@@ -155,7 +200,7 @@ func MergeConfig(userConf, imageConf *Config) error {
return nil
}
func parseLxcConfOpts(opts ListOpts) ([]KeyValuePair, error) {
func parseLxcConfOpts(opts utils.ListOpts) ([]KeyValuePair, error) {
out := make([]KeyValuePair, len(opts))
for i, o := range opts {
k, v, err := parseLxcOpt(o)
@@ -174,3 +219,131 @@ func parseLxcOpt(opt string) (string, string, error) {
}
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
}
func RootIsShared() bool {
if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
for _, line := range strings.Split(string(data), "\n") {
cols := strings.Split(line, " ")
if len(cols) >= 6 && cols[4] == "/" {
return strings.HasPrefix(cols[6], "shared")
}
}
}
// No idea, probably safe to assume so
return true
}
func BtrfsReflink(fd_out, fd_in uintptr) error {
res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in))
if res != 0 {
return syscall.Errno(res)
}
return nil
}
func CopyFile(dstFile, srcFile *os.File) error {
err := BtrfsReflink(dstFile.Fd(), srcFile.Fd())
if err == nil {
return nil
}
// Fall back to normal copy
_, err = io.Copy(dstFile, srcFile)
return err
}
// We will receive port specs in the format of ip:public:private/proto and these need to be
// parsed in the internal types
func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
exposedPorts := make(map[Port]struct{}, len(ports))
bindings := make(map[Port][]PortBinding)
for _, rawPort := range ports {
proto := "tcp"
if i := strings.LastIndex(rawPort, "/"); i != -1 {
proto = rawPort[i+1:]
rawPort = rawPort[:i]
}
if !strings.Contains(rawPort, ":") {
rawPort = fmt.Sprintf("::%s", rawPort)
} else if len(strings.Split(rawPort, ":")) == 2 {
rawPort = fmt.Sprintf(":%s", rawPort)
}
parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort)
if err != nil {
return nil, nil, err
}
containerPort := parts["containerPort"]
rawIp := parts["ip"]
hostPort := parts["hostPort"]
if containerPort == "" {
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
}
port := NewPort(proto, containerPort)
if _, exists := exposedPorts[port]; !exists {
exposedPorts[port] = struct{}{}
}
binding := PortBinding{
HostIp: rawIp,
HostPort: hostPort,
}
bslice, exists := bindings[port]
if !exists {
bslice = []PortBinding{}
}
bindings[port] = append(bslice, binding)
}
return exposedPorts, bindings, nil
}
// Splits a port in the format of port/proto
func splitProtoPort(rawPort string) (string, string) {
parts := strings.Split(rawPort, "/")
l := len(parts)
if l == 0 {
return "", ""
}
if l == 1 {
return "tcp", rawPort
}
return parts[0], parts[1]
}
func parsePort(rawPort string) (int, error) {
port, err := strconv.ParseUint(rawPort, 10, 16)
if err != nil {
return 0, err
}
return int(port), nil
}
func migratePortMappings(config *Config) error {
if config.PortSpecs != nil {
// We don't have to worry about migrating the bindings to the host
// This is our breaking change
ports, _, err := parsePortSpecs(config.PortSpecs)
if err != nil {
return err
}
config.PortSpecs = nil
if config.ExposedPorts == nil {
config.ExposedPorts = make(map[Port]struct{}, len(ports))
}
for k, v := range ports {
config.ExposedPorts[k] = v
}
}
return nil
}
// Links come in the format of
// name:alias
func parseLink(rawLink string) (map[string]string, error) {
return utils.PartParser("name:alias", rawLink)
}
+66
View File
@@ -22,6 +22,18 @@ import (
"time"
)
// ListOpts type
type ListOpts []string
func (opts *ListOpts) String() string {
return fmt.Sprint(*opts)
}
func (opts *ListOpts) Set(value string) error {
*opts = append(*opts, value)
return nil
}
// Go is a basic promise implementation: it wraps calls a function in a goroutine,
// and returns a channel which will later return the function's return value.
func Go(f func() error) chan error {
@@ -1029,6 +1041,41 @@ func (e *StatusError) Error() string {
return fmt.Sprintf("Status: %d", e.Status)
}
func quote(word string, buf *bytes.Buffer) {
// Bail out early for "simple" strings
if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") {
buf.WriteString(word)
return
}
buf.WriteString("'")
for i := 0; i < len(word); i++ {
b := word[i]
if b == '\'' {
// Replace literal ' with a close ', a \', and a open '
buf.WriteString("'\\''")
} else {
buf.WriteByte(b)
}
}
buf.WriteString("'")
}
// Take a list of strings and escape them so they will be handled right
// when passed as arguments to an program via a shell
func ShellQuoteArguments(args []string) string {
var buf bytes.Buffer
for i, arg := range args {
if i != 0 {
buf.WriteByte(' ')
}
quote(arg, &buf)
}
return buf.String()
}
func IsClosedError(err error) bool {
/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
* See:
@@ -1038,3 +1085,22 @@ func IsClosedError(err error) bool {
*/
return strings.HasSuffix(err.Error(), "use of closed network connection")
}
func PartParser(template, data string) (map[string]string, error) {
// ip:public:private
templateParts := strings.Split(template, ":")
parts := strings.Split(data, ":")
if len(parts) != len(templateParts) {
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
}
out := make(map[string]string, len(templateParts))
for i, t := range templateParts {
value := ""
if len(parts) > i {
value = parts[i]
}
out[t] = value
}
return out, nil
}
+20
View File
@@ -421,3 +421,23 @@ func TestDependencyGraph(t *testing.T) {
t.Fatalf("Expected [d], found %v instead", res[2])
}
}
func TestParsePortMapping(t *testing.T) {
data, err := PartParser("ip:public:private", "192.168.1.1:80:8080")
if err != nil {
t.Fatal(err)
}
if len(data) != 3 {
t.FailNow()
}
if data["ip"] != "192.168.1.1" {
t.Fail()
}
if data["public"] != "80" {
t.Fail()
}
if data["private"] != "8080" {
t.Fail()
}
}
+142 -52
View File
@@ -1,15 +1,15 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"path"
"runtime"
"strings"
"testing"
"runtime"
)
// This file contains utility functions for docker's unit test suite.
@@ -26,7 +26,7 @@ func mkRuntime(f Fataler) *Runtime {
pc, _, _, _ := runtime.Caller(1)
callerLongName := runtime.FuncForPC(pc).Name()
parts := strings.Split(callerLongName, ".")
callerShortName := parts[len(parts) - 1]
callerShortName := parts[len(parts)-1]
if globalTestID == "" {
globalTestID = GenerateID()[:4]
}
@@ -62,11 +62,17 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) {
if err := os.Remove(root); err != nil {
return nil, err
}
utils.Debugf("Copying %s to %s", unitTestStoreBase, root)
if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
utils.Debugf("ERROR: Copying %s to %s returned %s", unitTestStoreBase, root, err)
return nil, err
}
runtime, err = NewRuntimeFromDirectory(root, false)
config := &DaemonConfig{
GraphPath: root,
AutoRestart: false,
}
runtime, err = NewRuntimeFromDirectory(config)
if err != nil {
return nil, err
}
@@ -125,7 +131,7 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf
if config.Image == "_" {
config.Image = GetTestImage(r).ID
}
c, err := r.Create(config)
c, _, err := r.Create(config)
if err != nil {
return nil, nil, err
}
@@ -253,12 +259,12 @@ func TestMergeConfig(t *testing.T) {
}
}
if len(configUser.PortSpecs) != 3 {
t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs))
if len(configUser.ExposedPorts) != 3 {
t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs))
}
for _, portSpecs := range configUser.PortSpecs {
if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" {
t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs)
for portSpecs := range configUser.ExposedPorts {
if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
}
}
if len(configUser.Env) != 3 {
@@ -284,48 +290,6 @@ func TestMergeConfig(t *testing.T) {
}
}
func TestMergeConfigPublicPortNotHonored(t *testing.T) {
volumesImage := make(map[string]struct{})
volumesImage["/test1"] = struct{}{}
volumesImage["/test2"] = struct{}{}
configImage := &Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
PortSpecs: []string{"1111", "2222"},
Env: []string{"VAR1=1", "VAR2=2"},
Volumes: volumesImage,
}
volumesUser := make(map[string]struct{})
volumesUser["/test3"] = struct{}{}
configUser := &Config{
Dns: []string{"3.3.3.3"},
PortSpecs: []string{"1111:3333"},
Env: []string{"VAR2=3", "VAR3=3"},
Volumes: volumesUser,
}
MergeConfig(configUser, configImage)
contains := func(a []string, expect string) bool {
for _, p := range a {
if p == expect {
return true
}
}
return false
}
if !contains(configUser.PortSpecs, "2222") {
t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs)
t.Fail()
}
if !contains(configUser.PortSpecs, "1111:3333") {
t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs)
t.Fail()
}
}
func TestParseLxcConfOpt(t *testing.T) {
opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
@@ -342,3 +306,129 @@ func TestParseLxcConfOpt(t *testing.T) {
}
}
}
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "tcp" {
t.Logf("Expected tcp got %s", k.Proto())
t.Fail()
}
if k.Port() != "80" {
t.Logf("Expected 80 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "" {
t.Logf("Expected \"\" got %s", s.HostPort)
t.Fail()
}
if s.HostIp != "192.168.1.100" {
t.Fail()
}
}
}
func TestParseNetworkOptsPublic(t *testing.T) {
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "tcp" {
t.Logf("Expected tcp got %s", k.Proto())
t.Fail()
}
if k.Port() != "80" {
t.Logf("Expected 80 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "8080" {
t.Logf("Expected 8080 got %s", s.HostPort)
t.Fail()
}
if s.HostIp != "192.168.1.100" {
t.Fail()
}
}
}
func TestParseNetworkOptsUdp(t *testing.T) {
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "udp" {
t.Logf("Expected udp got %s", k.Proto())
t.Fail()
}
if k.Port() != "6000" {
t.Logf("Expected 6000 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "" {
t.Logf("Expected \"\" got %s", s.HostPort)
t.Fail()
}
if s.HostIp != "192.168.1.100" {
t.Fail()
}
}
}
+404
View File
@@ -0,0 +1,404 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sqlite provides access to the SQLite library, version 3.
package sqlite
/*
#cgo LDFLAGS: -lsqlite3
#include <sqlite3.h>
#include <stdlib.h>
// These wrappers are necessary because SQLITE_TRANSIENT
// is a pointer constant, and cgo doesn't translate them correctly.
// The definition in sqlite3.h is:
//
// typedef void (*sqlite3_destructor_type)(void*);
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
}
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
}
*/
import "C"
import (
"errors"
"fmt"
"reflect"
"strconv"
"time"
"unsafe"
)
type Errno int
func (e Errno) Error() string {
s := errText[e]
if s == "" {
return fmt.Sprintf("errno %d", int(e))
}
return s
}
var (
ErrError error = Errno(1) // /* SQL error or missing database */
ErrInternal error = Errno(2) // /* Internal logic error in SQLite */
ErrPerm error = Errno(3) // /* Access permission denied */
ErrAbort error = Errno(4) // /* Callback routine requested an abort */
ErrBusy error = Errno(5) // /* The database file is locked */
ErrLocked error = Errno(6) // /* A table in the database is locked */
ErrNoMem error = Errno(7) // /* A malloc() failed */
ErrReadOnly error = Errno(8) // /* Attempt to write a readonly database */
ErrInterrupt error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/
ErrIOErr error = Errno(10) // /* Some kind of disk I/O error occurred */
ErrCorrupt error = Errno(11) // /* The database disk image is malformed */
ErrFull error = Errno(13) // /* Insertion failed because database is full */
ErrCantOpen error = Errno(14) // /* Unable to open the database file */
ErrEmpty error = Errno(16) // /* Database is empty */
ErrSchema error = Errno(17) // /* The database schema changed */
ErrTooBig error = Errno(18) // /* String or BLOB exceeds size limit */
ErrConstraint error = Errno(19) // /* Abort due to constraint violation */
ErrMismatch error = Errno(20) // /* Data type mismatch */
ErrMisuse error = Errno(21) // /* Library used incorrectly */
ErrNolfs error = Errno(22) // /* Uses OS features not supported on host */
ErrAuth error = Errno(23) // /* Authorization denied */
ErrFormat error = Errno(24) // /* Auxiliary database format error */
ErrRange error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */
ErrNotDB error = Errno(26) // /* File opened that is not a database file */
Row = Errno(100) // /* sqlite3_step() has another row ready */
Done = Errno(101) // /* sqlite3_step() has finished executing */
)
var errText = map[Errno]string{
1: "SQL error or missing database",
2: "Internal logic error in SQLite",
3: "Access permission denied",
4: "Callback routine requested an abort",
5: "The database file is locked",
6: "A table in the database is locked",
7: "A malloc() failed",
8: "Attempt to write a readonly database",
9: "Operation terminated by sqlite3_interrupt()*/",
10: "Some kind of disk I/O error occurred",
11: "The database disk image is malformed",
12: "NOT USED. Table or record not found",
13: "Insertion failed because database is full",
14: "Unable to open the database file",
15: "NOT USED. Database lock protocol error",
16: "Database is empty",
17: "The database schema changed",
18: "String or BLOB exceeds size limit",
19: "Abort due to constraint violation",
20: "Data type mismatch",
21: "Library used incorrectly",
22: "Uses OS features not supported on host",
23: "Authorization denied",
24: "Auxiliary database format error",
25: "2nd parameter to sqlite3_bind out of range",
26: "File opened that is not a database file",
100: "sqlite3_step() has another row ready",
101: "sqlite3_step() has finished executing",
}
func (c *Conn) error(rv C.int) error {
if c == nil || c.db == nil {
return errors.New("nil sqlite database")
}
if rv == 0 {
return nil
}
if rv == 21 { // misuse
return Errno(rv)
}
return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
}
type Conn struct {
db *C.sqlite3
}
func Version() string {
p := C.sqlite3_libversion()
return C.GoString(p)
}
func Open(filename string) (*Conn, error) {
if C.sqlite3_threadsafe() == 0 {
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
}
var db *C.sqlite3
name := C.CString(filename)
defer C.free(unsafe.Pointer(name))
rv := C.sqlite3_open_v2(name, &db,
C.SQLITE_OPEN_FULLMUTEX|
C.SQLITE_OPEN_READWRITE|
C.SQLITE_OPEN_CREATE,
nil)
if rv != 0 {
return nil, Errno(rv)
}
if db == nil {
return nil, errors.New("sqlite succeeded without returning a database")
}
return &Conn{db}, nil
}
func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) {
dname := C.CString(dstTable)
sname := C.CString(srcTable)
defer C.free(unsafe.Pointer(dname))
defer C.free(unsafe.Pointer(sname))
sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname)
if sb == nil {
return nil, dst.error(C.sqlite3_errcode(dst.db))
}
return &Backup{sb, dst, src}, nil
}
type Backup struct {
sb *C.sqlite3_backup
dst, src *Conn
}
func (b *Backup) Step(npage int) error {
rv := C.sqlite3_backup_step(b.sb, C.int(npage))
if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked {
return nil
}
return Errno(rv)
}
type BackupStatus struct {
Remaining int
PageCount int
}
func (b *Backup) Status() BackupStatus {
return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))}
}
func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error {
var err error
for {
err = b.Step(npage)
if err != nil {
break
}
if c != nil {
c <- b.Status()
}
time.Sleep(period)
}
return b.dst.error(C.sqlite3_errcode(b.dst.db))
}
func (b *Backup) Close() error {
if b.sb == nil {
return errors.New("backup already closed")
}
C.sqlite3_backup_finish(b.sb)
b.sb = nil
return nil
}
func (c *Conn) BusyTimeout(ms int) error {
rv := C.sqlite3_busy_timeout(c.db, C.int(ms))
if rv == 0 {
return nil
}
return Errno(rv)
}
func (c *Conn) Exec(cmd string, args ...interface{}) error {
s, err := c.Prepare(cmd)
if err != nil {
return err
}
defer s.Finalize()
err = s.Exec(args...)
if err != nil {
return err
}
rv := C.sqlite3_step(s.stmt)
if Errno(rv) != Done {
return c.error(rv)
}
return nil
}
type Stmt struct {
c *Conn
stmt *C.sqlite3_stmt
err error
t0 time.Time
sql string
args string
}
func (c *Conn) Prepare(cmd string) (*Stmt, error) {
if c == nil || c.db == nil {
return nil, errors.New("nil sqlite database")
}
cmdstr := C.CString(cmd)
defer C.free(unsafe.Pointer(cmdstr))
var stmt *C.sqlite3_stmt
var tail *C.char
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail)
if rv != 0 {
return nil, c.error(rv)
}
return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil
}
func (s *Stmt) Exec(args ...interface{}) error {
s.args = fmt.Sprintf(" %v", []interface{}(args))
rv := C.sqlite3_reset(s.stmt)
if rv != 0 {
return s.c.error(rv)
}
n := int(C.sqlite3_bind_parameter_count(s.stmt))
if n != len(args) {
return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n))
}
for i, v := range args {
var str string
switch v := v.(type) {
case []byte:
var p *byte
if len(v) > 0 {
p = &v[0]
}
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
return s.c.error(rv)
}
continue
case bool:
if v {
str = "1"
} else {
str = "0"
}
default:
str = fmt.Sprint(v)
}
cstr := C.CString(str)
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
C.free(unsafe.Pointer(cstr))
if rv != 0 {
return s.c.error(rv)
}
}
return nil
}
func (s *Stmt) Error() error {
return s.err
}
func (s *Stmt) Next() bool {
rv := C.sqlite3_step(s.stmt)
err := Errno(rv)
if err == Row {
return true
}
if err != Done {
s.err = s.c.error(rv)
}
return false
}
func (s *Stmt) Reset() error {
C.sqlite3_reset(s.stmt)
return nil
}
func (s *Stmt) Scan(args ...interface{}) error {
n := int(C.sqlite3_column_count(s.stmt))
if n != len(args) {
return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n))
}
for i, v := range args {
n := C.sqlite3_column_bytes(s.stmt, C.int(i))
p := C.sqlite3_column_blob(s.stmt, C.int(i))
if p == nil && n > 0 {
return errors.New("got nil blob")
}
var data []byte
if n > 0 {
data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
}
switch v := v.(type) {
case *[]byte:
*v = data
case *string:
*v = string(data)
case *bool:
*v = string(data) == "1"
case *int:
x, err := strconv.Atoi(string(data))
if err != nil {
return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error())
}
*v = x
case *int64:
x, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error())
}
*v = x
case *float64:
x, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error())
}
*v = x
default:
return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
}
}
return nil
}
func (s *Stmt) SQL() string {
return s.sql + s.args
}
func (s *Stmt) Nanoseconds() int64 {
return time.Now().Sub(s.t0).Nanoseconds()
}
func (s *Stmt) Finalize() error {
rv := C.sqlite3_finalize(s.stmt)
if rv != 0 {
return s.c.error(rv)
}
return nil
}
func (c *Conn) Close() error {
if c == nil || c.db == nil {
return errors.New("nil sqlite database")
}
rv := C.sqlite3_close(c.db)
if rv != 0 {
return c.error(rv)
}
c.db = nil
return nil
}
+498
View File
@@ -0,0 +1,498 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sqlite3 provides access to the SQLite library, version 3.
//
// The package has no exported API.
// It registers a driver for the standard Go database/sql package.
//
// import _ "code.google.com/p/gosqlite/sqlite3"
//
// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.)
package sqlite
/*
#cgo LDFLAGS: -lsqlite3
#include <sqlite3.h>
#include <stdlib.h>
// These wrappers are necessary because SQLITE_TRANSIENT
// is a pointer constant, and cgo doesn't translate them correctly.
// The definition in sqlite3.h is:
//
// typedef void (*sqlite3_destructor_type)(void*);
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
}
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
}
*/
import "C"
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"io"
"strings"
"time"
"unsafe"
)
func init() {
sql.Register("sqlite3", impl{})
}
type errno int
func (e errno) Error() string {
s := errText[e]
if s == "" {
return fmt.Sprintf("errno %d", int(e))
}
return s
}
var (
errError error = errno(1) // /* SQL error or missing database */
errInternal error = errno(2) // /* Internal logic error in SQLite */
errPerm error = errno(3) // /* Access permission denied */
errAbort error = errno(4) // /* Callback routine requested an abort */
errBusy error = errno(5) // /* The database file is locked */
errLocked error = errno(6) // /* A table in the database is locked */
errNoMem error = errno(7) // /* A malloc() failed */
errReadOnly error = errno(8) // /* Attempt to write a readonly database */
errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/
errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */
errCorrupt error = errno(11) // /* The database disk image is malformed */
errFull error = errno(13) // /* Insertion failed because database is full */
errCantOpen error = errno(14) // /* Unable to open the database file */
errEmpty error = errno(16) // /* Database is empty */
errSchema error = errno(17) // /* The database schema changed */
errTooBig error = errno(18) // /* String or BLOB exceeds size limit */
errConstraint error = errno(19) // /* Abort due to constraint violation */
errMismatch error = errno(20) // /* Data type mismatch */
errMisuse error = errno(21) // /* Library used incorrectly */
errNolfs error = errno(22) // /* Uses OS features not supported on host */
errAuth error = errno(23) // /* Authorization denied */
errFormat error = errno(24) // /* Auxiliary database format error */
errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */
errNotDB error = errno(26) // /* File opened that is not a database file */
stepRow = errno(100) // /* sqlite3_step() has another row ready */
stepDone = errno(101) // /* sqlite3_step() has finished executing */
)
var errText = map[errno]string{
1: "SQL error or missing database",
2: "Internal logic error in SQLite",
3: "Access permission denied",
4: "Callback routine requested an abort",
5: "The database file is locked",
6: "A table in the database is locked",
7: "A malloc() failed",
8: "Attempt to write a readonly database",
9: "Operation terminated by sqlite3_interrupt()*/",
10: "Some kind of disk I/O error occurred",
11: "The database disk image is malformed",
12: "NOT USED. Table or record not found",
13: "Insertion failed because database is full",
14: "Unable to open the database file",
15: "NOT USED. Database lock protocol error",
16: "Database is empty",
17: "The database schema changed",
18: "String or BLOB exceeds size limit",
19: "Abort due to constraint violation",
20: "Data type mismatch",
21: "Library used incorrectly",
22: "Uses OS features not supported on host",
23: "Authorization denied",
24: "Auxiliary database format error",
25: "2nd parameter to sqlite3_bind out of range",
26: "File opened that is not a database file",
100: "sqlite3_step() has another row ready",
101: "sqlite3_step() has finished executing",
}
type impl struct{}
func (impl) Open(name string) (driver.Conn, error) {
if C.sqlite3_threadsafe() == 0 {
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
}
var db *C.sqlite3
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
rv := C.sqlite3_open_v2(cname, &db,
C.SQLITE_OPEN_FULLMUTEX|
C.SQLITE_OPEN_READWRITE|
C.SQLITE_OPEN_CREATE,
nil)
if rv != 0 {
return nil, errno(rv)
}
if db == nil {
return nil, errors.New("sqlite succeeded without returning a database")
}
return &conn{db: db}, nil
}
type conn struct {
db *C.sqlite3
closed bool
tx bool
}
func (c *conn) error(rv C.int) error {
if rv == 0 {
return nil
}
if rv == 21 || c.closed {
return errno(rv)
}
return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
}
func (c *conn) Prepare(cmd string) (driver.Stmt, error) {
if c.closed {
panic("database/sql/driver: misuse of sqlite driver: Prepare after Close")
}
cmdstr := C.CString(cmd)
defer C.free(unsafe.Pointer(cmdstr))
var s *C.sqlite3_stmt
var tail *C.char
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail)
if rv != 0 {
return nil, c.error(rv)
}
return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil
}
func (c *conn) Close() error {
if c.closed {
panic("database/sql/driver: misuse of sqlite driver: multiple Close")
}
c.closed = true
rv := C.sqlite3_close(c.db)
c.db = nil
return c.error(rv)
}
func (c *conn) exec(cmd string) error {
cstring := C.CString(cmd)
defer C.free(unsafe.Pointer(cstring))
rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil)
return c.error(rv)
}
func (c *conn) Begin() (driver.Tx, error) {
if c.tx {
panic("database/sql/driver: misuse of sqlite driver: multiple Tx")
}
if err := c.exec("BEGIN TRANSACTION"); err != nil {
return nil, err
}
c.tx = true
return &tx{c}, nil
}
type tx struct {
c *conn
}
func (t *tx) Commit() error {
if t.c == nil || !t.c.tx {
panic("database/sql/driver: misuse of sqlite driver: extra Commit")
}
t.c.tx = false
err := t.c.exec("COMMIT TRANSACTION")
t.c = nil
return err
}
func (t *tx) Rollback() error {
if t.c == nil || !t.c.tx {
panic("database/sql/driver: misuse of sqlite driver: extra Rollback")
}
t.c.tx = false
err := t.c.exec("ROLLBACK")
t.c = nil
return err
}
type stmt struct {
c *conn
stmt *C.sqlite3_stmt
err error
t0 time.Time
sql string
args string
closed bool
rows bool
colnames []string
coltypes []string
}
func (s *stmt) Close() error {
if s.rows {
panic("database/sql/driver: misuse of sqlite driver: Close with active Rows")
}
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt")
}
s.closed = true
rv := C.sqlite3_finalize(s.stmt)
if rv != 0 {
return s.c.error(rv)
}
return nil
}
func (s *stmt) NumInput() int {
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: NumInput after Close")
}
return int(C.sqlite3_bind_parameter_count(s.stmt))
}
func (s *stmt) reset() error {
return s.c.error(C.sqlite3_reset(s.stmt))
}
func (s *stmt) start(args []driver.Value) error {
if err := s.reset(); err != nil {
return err
}
n := int(C.sqlite3_bind_parameter_count(s.stmt))
if n != len(args) {
return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n)
}
for i, v := range args {
var str string
switch v := v.(type) {
case nil:
if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 {
return s.c.error(rv)
}
continue
case float64:
if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 {
return s.c.error(rv)
}
continue
case int64:
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 {
return s.c.error(rv)
}
continue
case []byte:
var p *byte
if len(v) > 0 {
p = &v[0]
}
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
return s.c.error(rv)
}
continue
case bool:
var vi int64
if v {
vi = 1
}
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 {
return s.c.error(rv)
}
continue
case time.Time:
str = v.UTC().Format(timefmt[0])
case string:
str = v
default:
str = fmt.Sprint(v)
}
cstr := C.CString(str)
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
C.free(unsafe.Pointer(cstr))
if rv != 0 {
return s.c.error(rv)
}
}
return nil
}
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: Exec after Close")
}
if s.rows {
panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows")
}
err := s.start(args)
if err != nil {
return nil, err
}
rv := C.sqlite3_step(s.stmt)
if errno(rv) != stepDone {
if rv == 0 {
rv = 21 // errMisuse
}
return nil, s.c.error(rv)
}
id := int64(C.sqlite3_last_insert_rowid(s.c.db))
rows := int64(C.sqlite3_changes(s.c.db))
return &result{id, rows}, nil
}
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: Query after Close")
}
if s.rows {
panic("database/sql/driver: misuse of sqlite driver: Query with active Rows")
}
err := s.start(args)
if err != nil {
return nil, err
}
s.rows = true
if s.colnames == nil {
n := int64(C.sqlite3_column_count(s.stmt))
s.colnames = make([]string, n)
s.coltypes = make([]string, n)
for i := range s.colnames {
s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i)))
s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i))))
}
}
return &rows{s}, nil
}
type rows struct {
s *stmt
}
func (r *rows) Columns() []string {
if r.s == nil {
panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows")
}
return r.s.colnames
}
const maxslice = 1<<31 - 1
var timefmt = []string{
"2006-01-02 15:04:05.999999999",
"2006-01-02T15:04:05.999999999",
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02 15:04",
"2006-01-02T15:04",
"2006-01-02",
}
func (r *rows) Next(dst []driver.Value) error {
if r.s == nil {
panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows")
}
rv := C.sqlite3_step(r.s.stmt)
if errno(rv) != stepRow {
if errno(rv) == stepDone {
return io.EOF
}
if rv == 0 {
rv = 21
}
return r.s.c.error(rv)
}
for i := range dst {
switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ {
default:
return fmt.Errorf("unexpected sqlite3 column type %d", typ)
case C.SQLITE_INTEGER:
val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i)))
switch r.s.coltypes[i] {
case "timestamp", "datetime":
dst[i] = time.Unix(val, 0).UTC()
case "boolean":
dst[i] = val > 0
default:
dst[i] = val
}
case C.SQLITE_FLOAT:
dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i)))
case C.SQLITE_BLOB, C.SQLITE_TEXT:
n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i)))
var b []byte
if n > 0 {
p := C.sqlite3_column_blob(r.s.stmt, C.int(i))
b = (*[maxslice]byte)(unsafe.Pointer(p))[:n]
}
dst[i] = b
switch r.s.coltypes[i] {
case "timestamp", "datetime":
dst[i] = time.Time{}
s := string(b)
for _, f := range timefmt {
if t, err := time.Parse(f, s); err == nil {
dst[i] = t
break
}
}
}
case C.SQLITE_NULL:
dst[i] = nil
}
}
return nil
}
func (r *rows) Close() error {
if r.s == nil {
panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows")
}
r.s.rows = false
r.s = nil
return nil
}
type result struct {
id int64
rows int64
}
func (r *result) LastInsertId() (int64, error) {
return r.id, nil
}
func (r *result) RowsAffected() (int64, error) {
return r.rows, nil
}
+1
View File
@@ -13,5 +13,6 @@ func displayFdGoroutines(t *testing.T) {
func TestFinal(t *testing.T) {
nuke(globalRuntime)
t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines)
cleanupDevMapper()
displayFdGoroutines(t)
}