Package io.fathom.cloud.compute.scheduler

Source Code of io.fathom.cloud.compute.scheduler.GawkerHost

package io.fathom.cloud.compute.scheduler;

import io.fathom.cloud.CloudException;
import io.fathom.cloud.blobs.BlobData;
import io.fathom.cloud.blobs.TempFile;
import io.fathom.cloud.compute.actions.ApplydContext;
import io.fathom.cloud.compute.actions.ConfigureFirewall;
import io.fathom.cloud.compute.actions.ConfigureIpset;
import io.fathom.cloud.compute.actions.ConfigureVirtualIp;
import io.fathom.cloud.compute.actions.network.VirtualIpMapper;
import io.fathom.cloud.compute.networks.IpRange;
import io.fathom.cloud.compute.networks.VirtualIp;
import io.fathom.cloud.compute.scheduler.HostFilesystem.Snapshot;
import io.fathom.cloud.compute.scheduler.LxcConfigBuilder.Volume;
import io.fathom.cloud.compute.scheduler.SshCommand.SshCommandExecution;
import io.fathom.cloud.compute.services.DatacenterManager;
import io.fathom.cloud.protobuf.CloudModel.FlavorData;
import io.fathom.cloud.protobuf.CloudModel.HostData;
import io.fathom.cloud.protobuf.CloudModel.InstanceData;
import io.fathom.cloud.protobuf.CloudModel.NetworkAddressData;
import io.fathom.cloud.protobuf.CloudModel.SecurityGroupData;
import io.fathom.cloud.services.ImageKey;
import io.fathom.cloud.services.ImageService;
import io.fathom.cloud.sftp.RemoteFile;
import io.fathom.cloud.sftp.Sftp;
import io.fathom.cloud.ssh.SftpChannel;
import io.fathom.cloud.ssh.SshConfig;

import java.io.File;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URI;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fathomdb.TimeSpan;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.net.InetAddresses;
import com.google.gson.Gson;

public class GawkerHost extends SchedulerHost {
    private static final Logger log = LoggerFactory.getLogger(InstanceScheduler.class);

    private final SshConfig sshConfig;

    private final ImmutableList<SchedulerHostNetwork> networks;

    private final File secretsDir;

    private final DatacenterManager datacenter;

    final HostFilesystem hostFilesystem;

    public GawkerHost(DatacenterManager datacenter, HostData hostInfo, SshConfig sshConfig) {
        super(hostInfo);
        this.datacenter = datacenter;
        this.sshConfig = sshConfig;

        this.networks = buildNetworks();

        this.secretsDir = new File("/var/fathomcloud/secrets/containers/");

        log.info("TODO: Auto-detect btrfs and use it!");
        this.hostFilesystem = new SimpleHostFilesystem(sshConfig);
    }

    @Override
    public ConfigurationOperation startConfiguration() throws CloudException {
        return new GawkerConfigurationOperation();
    }

    class GawkerConfigurationOperation implements ConfigurationOperation {
        private boolean updateApplyd = false;
        private final Sftp sftp;
        private final ApplydContext applydContext;

        public GawkerConfigurationOperation() throws CloudException {
            this.sftp = buildSftp();
            this.applydContext = new ApplydContext(sftp);
        }

        @Override
        public void configureFirewall(InstanceData instance, List<SecurityGroupData> securityGroups)
                throws CloudException {
            ConfigureFirewall configureFirewall = new ConfigureFirewall(GawkerHost.this, applydContext);

            // Write the security groups first, in case we have an error or
            // concurrent operation
            // (instances rules depend on security groups)
            for (SecurityGroupData securityGroup : securityGroups) {
                updateApplyd |= configureFirewall.updateConfig(securityGroup);
            }

            if (instance != null) {
                updateApplyd |= configureFirewall.updateConfig(instance);
            }
        }

        @Override
        public boolean applyChanges() throws CloudException {
            if (updateApplyd) {
                applydContext.apply(sshConfig);
                updateApplyd = false;
                return true;
            } else {
                return false;
            }
        }

        @Override
        public void close() throws IOException {
            sftp.close();
        }

        @Override
        public void removeFirewallConfig(InstanceData instance) throws CloudException {
            ConfigureFirewall configureFirewall = new ConfigureFirewall(GawkerHost.this, applydContext);
            updateApplyd |= configureFirewall.removeConfig(instance);
        }

        @Override
        public void attachVip(InstanceData instance, VirtualIp vip) throws CloudException {
            GawkerHost host = GawkerHost.this;

            VirtualIpMapper mapper = VirtualIpMapper.build(host, instance, vip);
            ConfigureVirtualIp configureVip = new ConfigureVirtualIp(host, applydContext);

            String hostIp = mapper.mapIp(host, instance, vip);
            updateApplyd |= configureVip.updateConfig(instance, vip, hostIp);
        }

        @Override
        public void detachVip(InstanceData instance, VirtualIp vip) throws CloudException {
            GawkerHost host = GawkerHost.this;

            VirtualIpMapper mapper = VirtualIpMapper.build(host, instance, vip);
            ConfigureVirtualIp configureVip = new ConfigureVirtualIp(host, applydContext);

            mapper.unmapIp(host, instance, vip);
            updateApplyd |= configureVip.removeConfig(vip);
        }

        @Override
        public void configureIpset(long securityGroupId, Set<String> ips) throws CloudException {
            ConfigureIpset conf = new ConfigureIpset(GawkerHost.this, applydContext);
            updateApplyd |= conf.updateConfig(securityGroupId, ips);
        }
    }

    @Override
    public void startContainer(UUID containerId) throws CloudException {
        try (Sftp sftp = buildSftp()) {
            GawkerProcess process = new GawkerProcess();

            File processFile = getProcessFile(containerId);

            process.Name = "/usr/bin/lxc-start";

            File configDir = getConfigDir(containerId);

            List<String> args = Lists.newArrayList();
            args.add("-n");
            args.add(containerId.toString());
            args.add("-f");
            args.add(new File(configDir, "config.lxc").getAbsolutePath());
            process.Args = args;

            process.Dir = hostFilesystem.getRootFs(containerId).getAbsolutePath();

            String json = new Gson().toJson(process);
            sftp.writeAtomic(new RemoteFile(processFile), json.getBytes(Charsets.UTF_8));
        } catch (IOException e) {
            throw new CloudException("Error starting container", e);
        }
    }

    private File getProcessFile(UUID containerId) {
        return new File("/etc/gawker/processes/vm-" + containerId);
    }

    private File getConfigDir(UUID containerId) {
        return new File("/var/fathomcloud/vms/" + containerId + "/");
    }

    private Sftp buildSftp() throws CloudException {
        return buildSftp(getSystemTempDir());
    }

    private Sftp buildSftp(RemoteFile tempDir) throws CloudException {
        SftpChannel sftpChannel;
        try {
            sftpChannel = sshConfig.getSftpChannel();
        } catch (IOException e) {
            throw new CloudException("Error connecting to host", e);
        }
        return new Sftp(sftpChannel, tempDir);
    }

    @Override
    public UUID createContainer(InstanceData instance, ImageService.Image image) throws CloudException {
        UUID containerId = UUID.randomUUID();

        {
            ContainerInfo container = new ContainerInfo();
            container.imageId = image.getUniqueKey();
            container.key = containerId.toString();

            LxcConfigBuilder lxcConfig = new LxcConfigBuilder();

            lxcConfig.hostname = "s" + containerId;
            lxcConfig.bridge = "virbr0";
            lxcConfig.rootfs = hostFilesystem.getRootFs(containerId).getAbsolutePath();

            lxcConfig.configDir = getConfigDir(containerId).getAbsolutePath();

            lxcConfig.memoryLimitMB = 1024;
            lxcConfig.swapMemoryLimitMB = lxcConfig.memoryLimitMB;
            lxcConfig.cpuShares = 128;

            if (instance.hasFlavor()) {
                FlavorData flavor = instance.getFlavor();
                if (flavor.hasRam()) {
                    lxcConfig.memoryLimitMB = flavor.getRam();
                }

                lxcConfig.swapMemoryLimitMB = lxcConfig.memoryLimitMB;
                if (flavor.hasSwap()) {
                    lxcConfig.swapMemoryLimitMB += flavor.getSwap();
                }

                if (flavor.hasVcpus()) {
                    lxcConfig.cpuShares *= flavor.getVcpus();
                }
            }

            List<NetworkAddressData> addresses = instance.getNetwork().getAddressesList();
            if (addresses != null && !addresses.isEmpty()) {
                NetworkAddressData bestIpv4 = null;
                NetworkAddressData bestIpv6 = null;

                for (NetworkAddressData address : addresses) {
                    InetAddress inetAddress = InetAddresses.forString(address.getIp());
                    if (inetAddress instanceof Inet4Address) {
                        if (bestIpv4 == null) {
                            bestIpv4 = address;
                        } else {
                            log.warn("Cannot choose between IPv4 addresses");
                        }
                    } else if (inetAddress instanceof Inet6Address) {
                        if (bestIpv6 == null) {
                            bestIpv6 = address;
                        } else {
                            log.warn("Cannot choose between IPv6 addresses");
                        }
                    } else {
                        throw new IllegalStateException();
                    }
                }
                if (bestIpv4 != null) {
                    lxcConfig.ipv4Gateway = bestIpv4.getGateway();
                    lxcConfig.ipv4 = bestIpv4.getIp() + "/" + bestIpv4.getPrefixLength();
                }

                if (bestIpv6 != null) {
                    lxcConfig.ipv6Gateway = bestIpv6.getGateway();
                    lxcConfig.ipv6 = bestIpv6.getIp() + "/" + bestIpv6.getPrefixLength();
                }

                if (bestIpv6 != null && bestIpv6.hasMacAddress()) {
                    lxcConfig.hwaddr = bestIpv6.getMacAddress();
                } else if (bestIpv4 != null && bestIpv4.hasMacAddress()) {
                    lxcConfig.hwaddr = bestIpv4.getMacAddress();
                }
            }

            container.lxcConfig = lxcConfig;

            // container.injectFiles = Lists.newArrayList();
            // if (instance.hasKeyPair()) {
            // KeyPairData keyPair = instance.getKeyPair();
            //
            // InjectFile injectFile = new InjectFile();
            // injectFile.path = "/root/.ssh/authorized_keys";
            // injectFile.contents =
            // keyPair.getPublicKey().getBytes(Charsets.US_ASCII);
            // injectFile.mode = 0700;
            // container.injectFiles.add(injectFile);
            // }

            createContainer(containerId, container);
        }
        return containerId;
    }

    private void createContainer(UUID containerId, ContainerInfo container) throws CloudException {
        try (Sftp sftp = buildSftp()) {
            File rootfsPath = hostFilesystem.getRootFs(containerId);
            hostFilesystem.copyImageToRootfs(container.imageId, rootfsPath);

            for (VolumeType volumeType : new VolumeType[] { VolumeType.Ephemeral, VolumeType.Persistent }) {
                File path = hostFilesystem.createVolume(volumeType, containerId);

                Volume volume = new Volume();
                volume.hostPath = path.getAbsolutePath();
                volume.instancePath = "/volumes/" + volumeType.name().toLowerCase();
                container.lxcConfig.volumes.add(volume);
            }

            File configDir = getConfigDir(containerId);
            sftp.mkdirs(configDir);

            // // Don't use atomic... we don't have the right tmp, and we don't
            // // need atomic yet
            // String json = new Gson().toJson(container);
            // WriteFile.with(sshConfig).from(json).to(new File(configDir,
            // "config.json")).run();

            // for (InjectFile injectFile : container.injectFiles) {
            // // It's a security issue both in terms of the container,
            // // but also it requires granting the fathomcloud user lots of
            // // permissions
            // log.warn("Injecting files is deprecated");
            //
            // File injectPath = new File(rootfsPath, injectFile.path);
            // WriteFile writer =
            // WriteFile.with(sshConfig).to(injectPath).from(injectFile.contents);
            //
            // if (injectFile.mode != 0) {
            // writer.chmod(injectFile.mode);
            // }
            //
            // writer.chown(0, 0);
            //
            // writer.withSudo().run();
            // }

            // Don't use atomic... we don't have the right tmp, and we don't
            String lxcConfig = container.lxcConfig.build();
            WriteFile.with(sshConfig).from(lxcConfig).to(new File(configDir, "config.lxc")).run();
        } catch (IOException e) {
            throw new CloudException("Error creating container", e);
        }
    }

    @Override
    public boolean stopContainer(UUID containerId) throws CloudException {
        try (Sftp sftp = buildSftp()) {
            File processFile = getProcessFile(containerId);
            sftp.delete(processFile);
            return true;
        } catch (IOException e) {
            throw new CloudException("Error stopping container", e);
        }
    }

    @Override
    public boolean hasImage(ImageKey imageId) throws IOException, CloudException {
        return hostFilesystem.hasImage(imageId);
    }

    private RemoteFile getSystemTempDir() {
        return new RemoteFile(new File("/tmp"));
    }

    @Override
    public void uploadImage(ImageKey imageId, BlobData imageData) throws IOException, CloudException {
        hostFilesystem.uploadImage(imageId, imageData);
    }

    @Override
    public List<SchedulerHostNetwork> getNetworks() {
        return networks;
    }

    private ImmutableList<SchedulerHostNetwork> buildNetworks() {
        // We have one public IPv6 network, and one private IPv4 network
        SchedulerHostNetwork ipv4;
        SchedulerHostNetwork ipv6;

        // The IPv6 network has ::1 as the host, and ::1 acts as
        // the gateway, unless we have configured a different gateway
        {
            final IpRange ipRange = IpRange.parse(hostData.getCidr());

            final InetAddress gateway;
            if (hostData.hasGateway()) {
                gateway = InetAddresses.forString(hostData.getGateway());
            } else {
                gateway = ipRange.getAddress();
            }

            ipv6 = new SchedulerHostNetwork() {
                @Override
                public InetAddress getGateway() {
                    return gateway;
                }

                @Override
                public IpRange getIpRange() {
                    return ipRange;
                }

                @Override
                public boolean isPublicNetwork() {
                    return true;
                }

                @Override
                public String getKey() {
                    return "ipv6";
                }

                @Override
                public SchedulerHost getHost() {
                    return GawkerHost.this;
                }
            };
        }

        // The IPv4 is private, and is really only useful for NATting.
        // It is always 100.64.0.0/10; 100.64.0.1 is always the gateway.
        {
            final IpRange ipRange = IpRange.parse("100.64.0.0/10");

            final InetAddress gateway = InetAddresses.forString("100.64.0.1");

            ipv4 = new SchedulerHostNetwork() {
                @Override
                public InetAddress getGateway() {
                    return gateway;
                }

                @Override
                public IpRange getIpRange() {
                    return ipRange;
                }

                @Override
                public boolean isPublicNetwork() {
                    return false;
                }

                @Override
                public String getKey() {
                    return "ipv4-nat";
                }

                @Override
                public SchedulerHost getHost() {
                    return GawkerHost.this;
                }
            };
        }

        return ImmutableList.of(ipv6, ipv4);
    }

    @Override
    public byte[] getSecret(UUID containerId, String key) throws IOException, CloudException {
        File containerDir = new File(secretsDir, containerId.toString());
        File secretFile = new File(containerDir, key);

        try (Sftp sftp = buildSftp()) {
            byte[] data = sftp.readAllBytes(secretFile);
            return data;
        }
    }

    @Override
    public void setSecret(UUID containerId, String key, byte[] data) throws IOException, CloudException {
        File containerDir = new File(secretsDir, containerId.toString());
        File secretFile = new File(containerDir, key);

        try (Sftp sftp = buildSftp()) {
            sftp.mkdirs(containerDir);

            WriteFile.with(sshConfig).from(data).to(secretFile).run();
        }
    }

    @Override
    public TempFile createImage(UUID containerId) throws IOException, CloudException {
        // TODO: Move to btrfs
        // TODO: Move to script

        File lxcPath = new File("/cgroup/lxc");

        LxcFreezer freezer = new LxcFreezer(lxcPath, containerId);
        freezer.setFrozen(true);

        try (Snapshot snapshot = hostFilesystem.snapshotImage(containerId)) {
            // We can unfreeze the VM now
            freezer.setFrozen(false);

            // TODO: Support side-load

            TempFile snapshotFile = snapshot.copyToFile();
            return snapshotFile;
        } finally {
            if (freezer.isFrozen()) {
                freezer.setFrozen(false);
            }
        }
    }

    class LxcFreezer {
        final File lxcCgroups;
        final UUID containerId;

        boolean frozen;

        public LxcFreezer(File lxcCgroups, UUID containerId) {
            this.lxcCgroups = lxcCgroups;
            this.containerId = containerId;
        }

        File getFreezerFile() {
            File containerPath = new File(lxcCgroups, containerId.toString());
            File freezerFile = new File(containerPath, "freezer.state");
            return freezerFile;
        }

        void setFrozen(boolean frozen) throws IOException, CloudException {
            File freezerFile = getFreezerFile();
            String s = (frozen ? "FROZEN" : "THAWED");

            int maxAttempts = 10;

            SshCommand writeCommand = new SshCommand(sshConfig, String.format("echo '%s' | sudo tee %s", s,
                    freezerFile.getAbsolutePath()));
            SshCommand readCommand = new SshCommand(sshConfig, String.format("sudo cat %s",
                    freezerFile.getAbsolutePath()));

            int attempt = 0;
            while (true) {
                if (attempt > maxAttempts) {
                    throw new IllegalStateException("Unable to change freeze/thaw state of container");
                }

                writeCommand.run();

                // try (OutputStream os = sftp.writeFile(freezerFile,
                // WriteMode.Overwrite)) {
                // os.write(s.getBytes(Charsets.ISO_8859_1));
                // }

                TimeSpan.fromMilliseconds(100).doSafeSleep();

                SshCommandExecution readExecution = readCommand.run();

                String newStateString = readExecution.getStdout();
                if (newStateString.trim().equalsIgnoreCase(s)) {
                    this.frozen = frozen;
                    return;
                }

                attempt++;
            }
        }

        public boolean isFrozen() {
            return frozen;
        }

    }

    @Override
    public void purgeInstance(UUID containerId) throws IOException, CloudException {
        // TODO: Move to script?

        // TODO: Check if running??

        hostFilesystem.purgeInstance(containerId);

        {
            File dir = getConfigDir(containerId);
            String command = String.format("sudo rm -rf %s", dir.getAbsolutePath());

            SshCommand sshCommand = new SshCommand(sshConfig, command);
            sshCommand.run();
        }

    }

    @Override
    public DatacenterManager getDatacenterManager() {
        return datacenter;
    }

    @Override
    public String fetchUrl(URI uri) throws IOException {
        ShellCommand shellCommand = ShellCommand.create("/usr/bin/wget", "-q", "-O", "-");
        shellCommand.argQuoted(uri.toString());

        SshCommand sshCommand = shellCommand.withSsh(sshConfig);
        SshCommandExecution execution = sshCommand.run();

        return execution.getStdout();
    }
}
TOP

Related Classes of io.fathom.cloud.compute.scheduler.GawkerHost

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.