Beispiel #1
0
    def status(self, with_loadbalancer_info=False):
        # Locking so that a create is not meddling with status
        s = self._get_from_provider()
        if s is None:
            raise AXNotFoundException("Could not find Route {}.{}".format(
                self.name, self.namespace))
        ep = self._get_ep_from_provider()
        if ep is None:
            raise AXNotFoundException(
                "Could not find Route Endpoint for {}.{}".format(
                    self.name, self.namespace))

        field_map = {
            "name": "metadata.name",
            "application": "metadata.namespace",
            "ip": "spec.cluster_ip"
        }

        if with_loadbalancer_info:
            field_map["loadbalancer"] = "status.load_balancer.ingress.hostname"

        ret = KubeObject.swagger_obj_extract(s, field_map)
        field_map = {"ips": "subsets.addresses.ip"}
        ret["endpoints"] = KubeObject.swagger_obj_extract(ep, field_map)
        return ret
Beispiel #2
0
    def _create_deployment_resources(self):
        res = AXResources()

        for route in self.spec.template.internal_routes:
            # ignore empty port spec
            if len(route.ports) == 0:
                logger.debug(
                    "Skipping internal route {} as port spec is empty".format(
                        route.name))
                continue
            ir = InternalRoute(route.name, self.application)
            ir.create(route.to_dict()["ports"],
                      selector={"deployment": self.name},
                      owner=self.name)
            res.insert(ir)
            logger.debug("Created route {}".format(ir))

        for route in self.spec.template.external_routes:

            dns_name = route.dns_name()
            if dns_name.endswith("."):
                dns_name = dns_name[:-1]

            r = ExternalRoute(dns_name, self.application,
                              {"deployment": self.name}, route.target_port,
                              route.ip_white_list, route.visibility)
            try:
                elb_addr = visibility_to_elb_addr(route.visibility)
                elb_name = visibility_to_elb_name(route.visibility)
            except AXNotFoundException:
                if route.visibility == ExternalRouteVisibility.VISIBILITY_WORLD:
                    raise AXNotFoundException(
                        "Could not find the public ELB. Please report this error to Applatix Support at [email protected]"
                    )
                else:
                    assert route.visibility == ExternalRouteVisibility.VISIBILITY_ORGANIZATION, "Only world and organization are currently supported as visibility attributes"
                    raise AXNotFoundException(
                        "Please create a private ELB using the template named 'ax_private_elb_creator_workflow' before using 'visibility=organization'"
                    )

            name = r.create(elb_addr, elb_name=elb_name)
            res.insert(r)
            logger.debug("Created external route {} for {}/{}/{}".format(
                name, self.application, self.name, dns_name))

        main_container = self.spec.template.get_main_container()
        for key_name, vol in iteritems(main_container.inputs.volumes):
            assert "resource_id" in vol.details, "Volume resource_id absent in volume details"
            name = vol.details.get("axrn", None)
            resource_id = vol.details.get("resource_id", None)
            assert name is not None and resource_id is not None, "axrn and resource_id are required details for volume {}".format(
                key_name)
            nv_res = AXNamedVolumeResource(name, resource_id)
            nv_res.create()
            res.insert(nv_res)
            logger.debug(
                "Using named volume resource {} in application {}".format(
                    name, self.application))

        return res
Beispiel #3
0
 def login(self, registry, username, password):
     """
     Returns a base64 encoded token of username and password
     only if login is successful else it raises exceptions
     """
     try:
         self._conn.login(username,
                          password=password,
                          registry=registry,
                          reauth=True)
     except APIError as e:
         code = e.response.status_code
         if code == 401:
             # on login failure it raises a docker.errors.APIError:
             # 401 Client Error: Unauthorized
             raise AXUnauthorizedException(e.explanation)
         elif code == 404:
             raise AXNotFoundException(e.explanation)
         elif code == 500:
             if "x509: certificate signed by unknown authority" in e.response.text:
                 raise AXIllegalArgumentException(
                     "Certificate signed by unknown authority for {}".
                     format(registry))
             else:
                 raise e
         else:
             raise e
     token = base64.b64encode("{}:{}".format(username, password))
     return token
Beispiel #4
0
 def get_record(self, name, type):
     for record in self.list_records():
         if record['Name'] == name and record['Type'] == type:
             return record
     raise AXNotFoundException(
         "Could not find record {} of type {} in hosted zone {}".format(
             name, type, self.name))
Beispiel #5
0
def describe_webhook():
    @retry(wait_exponential_multiplier=1000,
           stop_max_attempt_number=3)
    def _get_webhook_from_kube_with_retry():
        try:
            return kubectl.api.read_namespaced_service_status(namespace="axsys", name="axops-webhook")
        except ApiException as ae:
            if ae.status == 404:
                return None
            else:
                raise

    webhook = _get_webhook_from_kube_with_retry()
    if not webhook or not webhook.spec:
        raise AXNotFoundException("No webhook found")

    rst = {
        "port_spec": [],
        "ip_ranges": webhook.spec.load_balancer_source_ranges,
        "hostname": webhook.status.load_balancer.ingress[0].hostname
    }

    for p in webhook.spec.ports:
        rst["port_spec"].append({
            "name": p.name,
            "port": p.port,
            "targetPort": int(p.target_port)
        })

    return jsonify(rst)
Beispiel #6
0
    def status(self):
        """
        Get the status of the deployment.
        Returns: Returns the entire V1Deployment as a dict.
        If deployment is not found then this will raise an AXNotFoundException (404)
        """
        # STEP 1: Get status of deployment
        stat = self._deployment_status()
        if stat is None:
            raise AXNotFoundException("Deployment {} not found in application {}".format(self.name, self.application))

        dep_field_map = {
            "name": "metadata.name",
            "generation": "metadata.annotations.ax_generation",
            "desired_replicas": "status.replicas",
            "available_replicas": "status.available_replicas",
            "unavailable_replicas": "status.unavailable_replicas"
        }
        ret = KubeObject.swagger_obj_extract(stat, dep_field_map, serializable=True)

        # STEP 2: Get the pods for the deployment and events associated
        podlist = self._deployment_pods().items
        dep_events = self._app_obj.events(name=self.name)
        event_field_map = {
            "message": "message",
            "reason": "reason",
            "source": "source.component",
            "host": "source.host",
            "firstTS": "first_timestamp",
            "lastTS": "last_timestamp",
            "count": "count",
            "container": "involved_object.field_path",
            "type": "type"
        }
        ret["events"] = []
        for event in dep_events:
            ret["events"].append(KubeObject.swagger_obj_extract(event, event_field_map, serializable=True))

        ret["pods"] = []
        for pod in podlist or []:
            # fill pod status and containers
            pstatus = Pod.massage_pod_status(pod)

            # fill events for pod
            pstatus["events"] = []

            events = self._app_obj.events(name=pod.metadata.name)
            for event in events:
                pstatus["events"].append(KubeObject.swagger_obj_extract(event, event_field_map, serializable=True))

            # fill pod failure information for pod based on events
            pstatus["failure"] = Deployment._pod_failed_info(pstatus)

            ret["pods"].append(pstatus)

        # STEP 3: From the deployment spec get the resources created by deployment
        # TODO: Add this when services are created by deployment

        return ret
Beispiel #7
0
 def swagger_exception_handler(*args, **kwargs):
     try:
         return func(*args, **kwargs)
     except ApiException as e:
         if e.status == 404:
             raise AXNotFoundException(e.reason, detail=e.body)
         elif e.status == 409:
             raise AXConflictException(e.reason, detail=e.body)
         raise AXKubeApiException(e.reason, detail=e.body)
Beispiel #8
0
 def delete_registry(self, server):
     s = SecretsManager()
     dns_name = urlparse("https://{}".format(server)).netloc
     if s.get_imgpull(dns_name, "axuser"):
         s.delete_imgpull(dns_name, "axuser")
     else:
         raise AXNotFoundException(
             "Registry server {} not registered with platform".format(
                 dns_name))
Beispiel #9
0
    def get_labels(self):
        """
        Get a dict of labels used for this deployment
        """
        state = self._deployment_status()
        if state is None:
            raise AXNotFoundException("Did not find deployment {} in application {}".format(self.name, self.application))

        return KubeObject.swagger_obj_extract(state, {"labels": "spec.selector.match_labels"})['labels']
Beispiel #10
0
    def get_hosted_zone_id(self):
        if not self._id:
            zone = self.client.get_hosted_zone(self.name)
            if not zone:
                raise AXNotFoundException("Hosted zone {} not found".format(
                    self.name))
            self._id = zone.get_hosted_zone_id()

        return self._id
Beispiel #11
0
    def get_elb_addr(self, elb_name):
        logger.debug("Getting address {}".format(elb_name))
        with self._lock:
            params = {
                "elb_name": elb_name
            }
            elb_entries = self._db.retry_request('get', self._table, params=params, max_retry=5, retry_on_exception=self._db.get_retry_on_exception, value_only=True)
            for elb in elb_entries or []:
                return elb["elb_addr"]

        raise AXNotFoundException("Did not find an elb address for elb {}".format(elb_name))
Beispiel #12
0
 def add_elb_addr(self, elb_name, elb_addr):
     logger.debug("Adding address {} to elb {}".format(elb_addr, elb_name))
     with self._lock:
         params = {
             "elb_name": elb_name
         }
         elb_entries = self._db.retry_request('get', self._table, params=params, max_retry=5, retry_on_exception=self._db.get_retry_on_exception, value_only=True)
         if len(elb_entries) < 1:
             raise AXNotFoundException("Did not find an entry for {} in node port table".format(elb_name))
         for elb in elb_entries:
             elb["elb_addr"] = elb_addr
             self._db.retry_request('put', self._table, data=elb, max_retry=5, retry_on_exception=self._db.create_retry_on_exception, value_only=True)
Beispiel #13
0
    def create_alias_record(self, name, elb_addr, elb_name=None):
        """
        Create an alias record. This is idempotent as it uses UPSERT (create or update if exists)
        Args:
            name: The name of the alias. E.g. For zone test.acme.com if you want to create a alias alias1.test.acme.com
                  then name is alias1
            elb_addr: The ELB address to point to
            elb_hostedzoneid: the hosted zone ID of the ELB

        Returns:

        """
        name = name.lower()
        subdomain_name_check(name)

        @retry_exponential(noretry=[404])
        def get_load_balancer_info(elb_client, lb_name, elb_addr):
            for elb in elb_client.describe_load_balancers(
                    LoadBalancerNames=[lb_name])['LoadBalancerDescriptions']:
                if elb['DNSName'] == elb_addr:
                    return elb['CanonicalHostedZoneNameID']
            return None

        region = AXClusterConfig().get_region()
        elb_client = boto3.Session().client("elb", region_name=region)
        if elb_name is None:
            # try to infer from elb_addr
            # http://docs.aws.amazon.com/elasticloadbalancing/2012-06-01/APIReference/API_CreateLoadBalancer.html
            # From the link above "This name must be unique within your set of load balancers for the region, must have
            # a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and cannot begin or end with a hyphen."
            load_balancer_name = elb_addr[:32]
        else:
            load_balancer_name = elb_name

        elb_hostedzoneid = get_load_balancer_info(elb_client,
                                                  load_balancer_name, elb_addr)
        if elb_hostedzoneid is None:
            raise AXNotFoundException(
                "Could not find the ELB for the elb_addr {} in AWS".format(
                    elb_addr))

        self.get_hosted_zone_id()
        record = {
            "Name": "{}.{}".format(name, self.name),
            "Type": "A",
            "AliasTarget": {
                'DNSName': elb_addr,
                'HostedZoneId': elb_hostedzoneid,
                'EvaluateTargetHealth': False
            }
        }
        self.client.change_record(self._id, "UPSERT", record)
Beispiel #14
0
 def exception_handler(*args, **kwargs):
     try:
         return func(*args, **kwargs)
     except botocore.exceptions.ClientError as e:
         code = e.response["ResponseMetadata"]["HTTPStatusCode"]
         msg = e.message
         if code == 409:
             raise AXConflictException(msg)
         elif code == 404 or code == 400:
             # 400 also seems to be used for not found by AWS
             raise AXNotFoundException(msg)
         else:
             raise AXPlatformException(msg)
Beispiel #15
0
    def delete(self, aws_resources_only=False):

        (application, deployment) = self._get_app_and_dep()
        if application is None or deployment is None:
            raise AXNotFoundException("Could not find application and deployment for deleting managed elb {}".format(self.name))


        if not aws_resources_only:
            # Start by deleting NodeRoute Objects first
            logger.debug("Deleting node routes for the managed elb {}".format(self.name))
            nr_name = self.name
            noderoute = NodeRoute(nr_name, application)
            with ManagedElbNodeOperation(noderoute):
                if noderoute.exists():
                    noderoute.delete()

            # Release all the nodeport after the NodeRoutes are gone
            logger.debug("Releasing nodeports for the managed elb {}".format(self.name))
            self._npm.release_all(self.name)

        # Now unterraform to get rid of the ELB
        init_command = 'terraform init -backend=true -backend-config="bucket={}" -backend-config="key={}managed_elbs/{}.tfstate" -backend-config="region={}"  -reconfigure'.format(
            self.bucket, self.terraform_dir, self.name, self.region
        )

        terraform_command = "terraform destroy --force -var 'asg_name={}' -var 'cluster_id={}' -var 'cidrs=[]' -var 'elb_prefix={}' -var 'ports=[]' -var 'protocols=[]' -var 'region={}' -var 'application={}' -var 'deployment={}'".format(
            self._get_asg_name(application), self.name_id, self.name, self.region, application, deployment
        )

        tempdir = tempfile.mkdtemp()
        self.s3.download_file("{}managed_elbs/{}.tf".format(self.terraform_dir, self.name), "{}/elb.tf".format(tempdir))
        logger.debug("Terraform init\n{}".format(subprocess.check_output(init_command, shell=True, cwd=tempdir)))
        logger.debug("Terraform destroy\n{}".format(subprocess.check_output(terraform_command, shell=True, cwd=tempdir)))
        shutil.rmtree(tempdir, ignore_errors=True)

        # delete the terraform state and config file
        self.s3.delete_object("{}managed_elbs/{}.tf".format(self.terraform_dir, self.name))
        self.s3.delete_object("{}managed_elbs/{}.tfstate".format(self.terraform_dir, self.name))
Beispiel #16
0
    def create(self, application, deployment, ports_spec, internal=True, labels=None):

        self._creation_prechecks(application, deployment)

        # Create the port spec for the NodeRoute and get node ports for each of the listener ports and generate
        # the listener spec and the health check spec
        listener_spec = ""
        health_check_spec = None
        i = 0
        srv_pspec = []
        listen_ports = []
        protocols = []
        for port in ports_spec:
            # Build the port spec for NodeRoute
            curr_port = {}
            curr_port["name"] = "port-{}".format(i)
            curr_port["port"] = port["listen_port"]
            curr_port["target_port"] = port["container_port"]
            curr_port["node_port"] = self._npm.get(self.name, port["listen_port"])
            srv_pspec.append(curr_port)

            # Build array of listen ports and protocol for security rules
            listen_ports.append(port["listen_port"])
            protocols.append(port["protocol"])

            # Build listener spec for ELB
            ssl_cert = ""
            if "https" in port["protocol"] or "ssl" in port["protocol"]:
                ssl_cert = "ssl_certificate_id = \"{}\"".format(port["certificate"])
            listener_spec += LISTENER_TEMPLATE.format(curr_port["node_port"], self.protocol_map[port["protocol"]], port["listen_port"], port["protocol"], ssl_cert)

            # Build health check spec for ELB
            if health_check_spec is None:
                health_check_spec = HEALTHCHECK_TEMPLATE.format(curr_port["node_port"])

            i += 1

        # create the nodeport k8s service
        # Note: We generate a random string [a-z] rather than using the elb name (even though elb name needs to be unique
        #       w.r.t other elb names, because we need to generate a kubernetes service object. It is possible that the
        #       user has generated an internal route with that name and we do not want to clobber the internal route
        nr_name = self.name
        noderoute = NodeRoute(nr_name, application)

        # Get a lock for operations of exist() delete() and create() as the InternalRouteOperation will not be
        # atomic across all these operations.
        with ManagedElbNodeOperation(noderoute):
            if noderoute.exists():
                noderoute.delete()
            noderoute.create(srv_pspec, selector=labels)

        # modify the elb template using the pass port specification
        my_elb_template = AWS_ELB_TEMPLATE.replace("# AXMODIFY-LISTENERS", listener_spec)
        my_elb_template = my_elb_template.replace("# AXMODIFY-HEALTHCHECK", health_check_spec)

        # Create cloud specific resources using terraform
        init_command = 'terraform init -backend=true -backend-config="bucket={}" -backend-config="key={}managed_elbs/{}.tfstate" -backend-config="region={}"  -reconfigure'.format(
            self.bucket, self.terraform_dir, self.name, self.region
        )

        terraform_command = "terraform apply -var 'asg_name={}' -var 'cluster_id={}' -var 'cidrs={}' -var 'elb_prefix={}' -var 'ports={}' -var 'protocols={}' -var 'region={}' -var 'application={}' -var 'deployment={}' -var 'elb_internal={}'".format(
            self._get_asg_name(application), self.name_id, json.dumps(self.trusted_cidrs), self.name, json.dumps(listen_ports), json.dumps(protocols), self.region, application, deployment, (str(internal)).lower()
        )

        logger.debug("Terraform spec\n{}".format(my_elb_template))
        logger.debug("Terraform init command is: {}".format(init_command))
        logger.debug("Terraform command is: {}".format(terraform_command))

        tempdir = tempfile.mkdtemp()
        with open("{}/elb.tf".format(tempdir), "w") as f:
            f.write(my_elb_template)

        # also write the file to S3
        self.s3.put_object("{}managed_elbs/{}.tf".format(self.terraform_dir, self.name), my_elb_template)

        logger.debug("Terraform init\n{}".format(subprocess.check_output(init_command, shell=True, cwd=tempdir)))
        logger.debug("Terraform apply\n{}".format(subprocess.check_output(terraform_command, shell=True, cwd=tempdir)))
        shutil.rmtree(tempdir, ignore_errors=True)

        elb = self._get_load_balancer_info()
        if elb is None:
            raise AXNotFoundException(
                "Created ELB {} was not found. That is odd. Please try creating again".format(self.name))
        dnsname = elb["DNSName"]
        self._npm.add_elb_addr(self.name, dnsname)

        return dnsname