| |
| # |
| # Copyright 2016 RIFT.IO Inc |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| import glob |
| import itertools |
| import os |
| |
| import boto |
| import boto.vpc |
| |
| # TODO: Pull the lastest of owned instances. |
| __default_instance_ami__ = 'ami-e421bc8c' |
| |
| # TODO: Make VPC's per user? |
| __default_subnet__ = 'subnet-4b484363' |
| __default_security_group__ = 'sg-d9da90bc' |
| |
| __default_instance_type__ = 'm1.medium' |
| __default_vpc__ = 'vpc-e7ed4482' |
| |
| class RWEC2(object): |
| def __init__(self, subnet=None, ami=None): |
| self._subnet = subnet if subnet is not None else __default_subnet__ |
| self._ami = ami if ami is not None else __default_instance_ami__ |
| |
| self._conn = boto.connect_ec2() |
| |
| @staticmethod |
| def cloud_init_current_user(): |
| """ |
| Return user_data configuration suitable for cloud-init that will create a user |
| with sudo and ssh key access on the remote instance. |
| |
| ssh keys are found with the glob ~/.ssh/*pub* |
| """ |
| user_data = "users:\n" |
| user_data += " - name: %s\n" % (os.getlogin(),) |
| user_data += " groups: [wheel, adm, systemd-journal]\n" |
| user_data += " sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n" |
| user_data += " shell: /bin/bash\n" |
| user_data += " ssh_authorized_keys:\n" |
| for pub_key in glob.glob('%s/.ssh/*pub*' % (os.environ['HOME'],)): |
| with open(pub_key) as fp: |
| user_data += " - %s" % (fp.read(),) |
| |
| return user_data |
| |
| |
| @staticmethod |
| def cloud_init_yum_repos(): |
| """ |
| Return a string of user_data commands that can be used to update the yum |
| repos to point to the correct location. They should be added by the caller |
| within a 'runcmd:' block. |
| """ |
| ret = " - sed -i -e 's,www\.,,' -e 's,riftio\.com/mirrors,riftio.com:8881,' /etc/yum.repos.d/*.repo\n" |
| return ret |
| |
| def instances(self, cluster_component, cluster_instance): |
| """ |
| List of instances owned by the given cluster instance |
| |
| @param cluster_component - parent cluster of each instance |
| @param cluster_instance - instance id of the owning cluster |
| @param n_instances - number of requested instances |
| |
| @return - list of boto.ec2.instance.Instances provisioned |
| """ |
| ret = [] |
| reservations = self._conn.get_all_instances() |
| for instance in [instance for reservation in reservations for instance in reservation.instances]: |
| tags = instance.tags |
| if (tags.get('parent_component') == cluster_component |
| and tags.get('parent_instance') == cluster_instance): |
| ret.append(instance) |
| |
| return ret |
| |
| def provision_master(self, cluster_component, cluster_instance): |
| """ |
| Provision a master instance in EC2. The master instance is a special instance with the |
| following features: |
| - Public IP |
| - /home shared over NFS |
| |
| @param cluster_component - parent cluster of each instance |
| @param cluster_instance - instance id of the owning cluster |
| |
| @return - boto.ec2.instance.Instances provisioned |
| """ |
| vpc = boto.vpc.VPCConnection() |
| subnet = vpc.get_all_subnets(subnet_ids=__default_subnet__)[0] |
| cidr_block = subnet.cidr_block |
| vpc.close() |
| |
| user_data = "#cloud-config\n" |
| user_data += "runcmd:\n" |
| user_data += " - echo '/home %s(rw,root_squash,sync)' > /etc/exports\n" % (cidr_block,) |
| user_data += " - systemctl start nfs-server\n" |
| user_data += " - systemctl enable nfs-server\n" |
| user_data += self.cloud_init_yum_repos() |
| user_data += self.cloud_init_current_user() |
| |
| |
| net_if = boto.ec2.networkinterface.NetworkInterfaceSpecification( |
| subnet_id=__default_subnet__, |
| groups=[__default_security_group__,], |
| associate_public_ip_address=True) |
| |
| net_ifs = boto.ec2.networkinterface.NetworkInterfaceCollection(net_if) |
| |
| new_reservation = self._conn.run_instances( |
| image_id=self._ami, |
| min_count=1, |
| max_count=1, |
| instance_type=__default_instance_type__, |
| network_interfaces=net_ifs, |
| tenancy='default', |
| user_data=user_data) |
| instance = new_reservation.instances[0] |
| |
| instance.add_tag('parent_component', cluster_component) |
| instance.add_tag('parent_instance', cluster_instance) |
| instance.add_tag('master', 'self') |
| |
| return instance |
| |
| |
| def provision(self, cluster_component, cluster_instance, n_instances=1, master_instance=None, net_ifs=None): |
| """ |
| Provision a number of EC2 instanced to be used in a cluster. |
| |
| @param cluster_component - parent cluster of each instance |
| @param cluster_instance - instance id of the owning cluster |
| @param n_instances - number of requested instances |
| @param master_instance - if specified, the boto.ec2.instance.Instance that is providing master |
| services for this cluster |
| |
| @return - list of boto.ec2.instance.Instances provisioned |
| """ |
| instances = [] |
| cluster_instance = int(cluster_instance) |
| |
| def posess_instance(instance): |
| instances.append(instance) |
| instance.add_tag('parent_component', cluster_component) |
| instance.add_tag('parent_instance', cluster_instance) |
| if master_instance is not None: |
| instance.add_tag('master', master_instance.id) |
| else: |
| instance.add_tag('master', 'None') |
| |
| user_data = "#cloud-config\n" |
| user_data += self.cloud_init_current_user() |
| user_data += "runcmd:\n" |
| user_data += self.cloud_init_yum_repos() |
| |
| if master_instance is not None: |
| user_data += " - echo '%s:/home /home nfs rw,soft,sync 0 0' >> /etc/fstab\n" % ( |
| master_instance.private_ip_address,) |
| user_data += " - mount /home\n" |
| |
| if net_ifs is not None: |
| kwds = {'subnet_id': __default_subnet__} |
| else: |
| kwds = {'network_interfaces': net_ifs} |
| print(net_ifs) |
| |
| new_reservation = self._conn.run_instances( |
| image_id=self._ami, |
| min_count=n_instances, |
| max_count=n_instances, |
| instance_type=__default_instance_type__, |
| tenancy='default', |
| user_data=user_data, |
| network_interfaces=net_ifs) |
| |
| _ = [posess_instance(i) for i in new_reservation.instances] |
| |
| return instances |
| |
| def stop(self, instance_id, free_resources=True): |
| """ |
| Stop the specified instance, freeing all allocated resources (elastic ips, etc) if requested. |
| |
| @param instance_id - name of the instance to stop |
| @param free_resource - If True that all resources that were only owned by this instance |
| will be deallocated as well. |
| """ |
| self._conn.terminate_instances(instance_ids=[instance_id,]) |
| |
| def fastpath111(self): |
| vpc_conn = boto.vpc.VPCConnection() |
| vpc = vpc_conn.get_all_vpcs(vpc_ids=[__default_vpc__,])[0] |
| subnet_addrs_split = vpc.cidr_block.split('.') |
| |
| networks = { |
| 'mgmt': [s for s in vpc_conn.get_all_subnets() if s.id == __default_subnet__][0], |
| 'tg_fabric': None, |
| 'ts_fabric': None, |
| 'tg_lb_ext': None, |
| 'lb_ts_ext': None, |
| } |
| |
| for i, network in enumerate([n for n, s in networks.items() if s == None]): |
| addr = "%s.%s.10%d.0/25" % (subnet_addrs_split[0], subnet_addrs_split[1], i) |
| try: |
| subnet = vpc_conn.create_subnet(vpc.id, addr) |
| except boto.exception.EC2ResponseError as e: |
| if 'InvalidSubnet.Conflict' == e.error_code: |
| subnet = vpc_conn.get_all_subnets(filters=[('vpcId', vpc.id), ('cidrBlock', addr)])[0] |
| else: |
| raise |
| |
| networks[network] = subnet |
| |
| def create_interfaces(nets): |
| ret = boto.ec2.networkinterface.NetworkInterfaceCollection() |
| |
| for i, network in enumerate(nets): |
| spec = boto.ec2.networkinterface.NetworkInterfaceSpecification( |
| subnet_id=networks[network].id, |
| description='%s iface' % (network,), |
| groups=[__default_security_group__], |
| device_index=i) |
| ret.append(spec) |
| |
| return ret |
| |
| ret = {} |
| |
| ret['cli'] = self.provision_master('fp111', 1) |
| ret['cli'].add_tag('Name', 'cli') |
| |
| net_ifs = create_interfaces(['mgmt']) |
| ret['mgmt'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0] |
| ret['mgmt'].add_tag('Name', 'mgmt') |
| |
| net_ifs = create_interfaces(['mgmt', 'tg_fabric']) |
| ret['tg1'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0] |
| ret['tg1'].add_tag('Name', 'tg1') |
| |
| net_ifs = create_interfaces(['mgmt', 'tg_fabric', 'tg_lb_ext']) |
| ret['tg2'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0] |
| ret['tg2'].add_tag('Name', 'tg2') |
| |
| net_ifs = create_interfaces(['mgmt', 'ts_fabric']) |
| ret['ts1'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0] |
| ret['ts1'].add_tag('Name', 'ts1') |
| |
| net_ifs = create_interfaces(['mgmt', 'ts_fabric', 'lb_ts_ext']) |
| ret['ts3'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0] |
| ret['ts3'].add_tag('Name', 'ts3') |
| |
| net_ifs = create_interfaces(['mgmt', 'ts_fabric', 'lb_ts_ext', 'tg_lb_ext']) |
| ret['ts2'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0] |
| ret['ts2'].add_tag('Name', 'ts2') |
| |
| return ret |
| |
| # vim: sw=4 |